C# CmdLet binary | add $env:Variable directly on import - c#

i am writing a c# binary cmdlet. at end it should installed on machine for immediate using when starting the ISE or PS-Console without Import-Module.
now i want to provide machine-wide some own $env:Variables like ex. $env:ProgramFiles. how i can do this?
Thanks!
EDIT:
for a more descriptive example here a code sniped:
namespace InstallTools.UpdateEnvironment
{
[Cmdlet(VerbsData.Update, "Environment")]
[OutputType(typeof(UpdateEnvironment))]
public class UpdateEnvironment : PSCmdlet
{
[Parameter(Position = 1,
Mandatory = false,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
ParameterSetName = "ENVIRONMENT")]
public SwitchParameter SetEnvironment { get; set; }
protected override void ProcessRecord()
{
if(SetEnvironment .IsPresent)
{
Environment.SetEnvironmentVariable("CDir", this.MyInvocation.PSScriptRoot);
Environment.SetEnvironmentVariable("CurrentTime", DateTime.Now.ToString("yyyy.MM.dd HH:mm:ss"));
}
}
}
}
if i call in PS Update-Environment -SetEnvironment all the doings will executed. in my case, Environment.SetEnvironmentVariable ("test", "testval") causes an $env:test to be available at runtime.
However, I want the variables to be initialized automatically when the ISE is opened, without calling Update-Environment.
Thanks at all!!

For immediate usage of your module when starting the ISE or PS-Console typing Import-Module you can modify the PSModulePath environment variable.
According to Modifying the PSModulePath Installation Path.
The PSModulePath environment variable stores the paths to the locations of the modules that are installed on disk. PowerShell uses this variable to locate modules when the user does not specify the full path to a module. The paths in this variable are searched in the order in which they appear.
When PowerShell starts, PSModulePath is created as a system environment variable with the following default value: $HOME\Documents\PowerShell\Modules; $PSHOME\Modules on Windows and $HOME/.local/share/powershell/Modules: usr/local/share/powershell/Modules on Linux or Mac, and $HOME\Documents\WindowsPowerShell\Modules; $PSHOME\Modules for Windows PowerShell.
This article also answered the second part of your question :
To add paths to the PSModulePath environement variable, use one of the 3 following methods:
1) To add a temporary value that is available only for the current session, run the following command at the command line:
$env:PSModulePath = $env:PSModulePath + "$([System.IO.Path]::PathSeparator)$MyModulePath"
2) To add a persistent value that is available whenever a session is opened, add the above command to a PowerShell profile file ($PROFILE)>
$profile.AllUsersAllHosts
C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1
For more information about profiles, see about_Profiles.
3) To add a persistent variable to the registry, create a new user environment variable called PSModulePath using the Environment Variables Editor in the System Properties dialog box.
To add a persistent variable by using a script, use the .Net method SetEnvironmentVariable on the System.Environment class. For example, the following script adds the C:\Program Files\Fabrikam\Module path to the value of the PSModulePath environment variable for the computer. To add the path to the user PSModulePath environment variable, set the target to "User".
$CurrentValue = [Environment]::GetEnvironmentVariable("PSModulePath", "Machine")
[Environment]::SetEnvironmentVariable("PSModulePath", $CurrentValue + [System.IO.Path]::PathSeparator + "C:\Program Files\Fabrikam\Modules", "Machine")

Related

How do I get windows registry property values using a PowerShell script run through a .Net PowerShell object?

I am using the following PowerShell script to return information about apps installed on a PC:
Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall"
When I run this script in the Windows Powershell ISE, I see something like this for results:
Hive: HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall
Name Property
---- -------- Connection Manager SystemComponent : 1
DirectDrawEx
Docker Desktop DisplayIcon : C:\Program
Files\Docker\Docker\Docker Desktop Installer.exe
DisplayName : Docker Desktop
DisplayVersion : 2.1.0.4
Version : 39773
InstallLocation : C:\Program Files\Docker\Docker
NoModify : 1
NoRepair : 1
Publisher : Docker Inc.
ChannelName : stable
ChannelUrl : https://download.docker.com/win/stable/appcast.xml
UninstallString : "C:\Program Files\Docker\Docker\Docker Desktop
Installer.exe" uninstall
I am calling the script from a C# .Net application as follows:
using (var ps = PowerShell.Create())
{
ps.Runspace = p_runspace;
ps.AddScript("Get-ChildItem ""HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall""");
IEnumerable<PSObject> commandOutput = ps.Invoke();
}
How do I programmatically get access in C# to the properties returned when I run the script (Publisher, DisplayName, DisplayVersion) returned when I run the script in the ISE?
I examined the properties on the PSObject in my app, they do not match up with the ISE.
Thanks, JohnB
Objects returned from PowerShell when invoked using PowerShell.Invoke() don't don't actually have all of the same members at the top level of the object, a bit of PowerShell "magic" makes it work that way from within a PowerShell session.
If you want a property value you need to inspect the Properties property of the returned PSObject:
foreach(PSObject obj in commandOutput) {
Object val = obj.Properties['PropertyName'].Value
}
where PropertyName is the name of the property you want.

How to make a registry entry using C# cake?

I need to create a registry entry based on finding of 32/64-bit system from cake script. I can see the File operations reference, Directory operations reference in C# cake site. But i could not find the registry related reference in C# cake. Could anyone please let me know is there any option to make a registry entry using C# cake? If so, please specify the reference link. This will help me a lot to continue in cake script.
An alternative to using C# you could also be using the Reg.exe shipped with all major versions of Windows.
You could use this tool with Cake using StartProcess alias.
An example of doing this below:
DirectoryPath system32Path = Context.Environment
.GetSpecialPath(SpecialPath.Windows)
.Combine("System32");
FilePath regPath = system32Path.CombineWithFilePath("reg.exe");
string keyName = "HKEY_CURRENT_USER\\Software\\Cake";
string valueName = "Rocks";
string valueData = "1";
ProcessSettings regSettings = new ProcessSettings()
.WithArguments(
arguments => arguments
.Append("add")
.AppendQuoted(keyName)
.Append("/f")
.AppendSwitchQuoted("/v", valueName)
.AppendSwitchQuoted("/t", "REG_DWORD")
.AppendSwitchQuoted("/d", valueData)
);
int result = StartProcess(regPath, regSettings);
if (result == 0)
{
Information("Registry value successfully set");
}
else
{
Information("Failed to set registry value");
}
Currently, there are no Cake aliases for working with the registry. Having said that, there is nothing to stop you manipulating the Registry directly using that standard C# types.
An example of one such approach is here:
Writing to registry in a C# application
Cake provides a number of aliases for things that are more complicated to do, however, remember that almost everything that is provided in an alias could be done directly with C# in your main script. The aliases are simply there as a convenience.

When writing a PowerShell module in C#, how do I store module state?

I'm writing a PowerShell module in C# that connects to a database. The module has a Get-MyDatabaseRecord cmdlet which can be used to query the database. If you have a PSCredential object in the variable $MyCredentials, you can call the cmdlet like so:
PS C:\> Get-MyDatabaseRecord -Credential $MyCredentials -Id 3
MyRecordId : 3
MyRecordValue : test_value
The problem is, having to specify the Credential parameter every time that you call Get-MyDatabaseRecord is tedious and inefficient. It would be better if you could just call one cmdlet to connect to the database, and then another to get the record:
PS C:\> Connect-MyDatabase -Credential $MyCredentials
PS C:\> Get-MyDatabaseRecord -Id 3
MyRecordId : 3
MyRecordValue : test_value
In order for that to be possible, the Connect-MyDatabase cmdlet has to store the database connection object somewhere so that the Get-MyDatabaseRecord cmdlet can obtain that object. How should I do this?
Ideas I've thought of
Use a static variable
I could just define a static variable somewhere to contain the database connection:
static class ModuleState
{
internal static IDbConnection CurrentConnection { get; set; }
}
However, global mutable state is usually a bad idea. Could this cause problems somehow, or is this a good solution?
(One example of a problem would be if multiple PowerShell sessions somehow shared the same instance of my assembly. Then all of the sessions would inadvertently share a single CurrentConnection property. But I don't know if this is actually possible.)
Use PowerShell module session state
The MSDN page "Windows PowerShell Session State" talks about something called session state. The page says that "session-state data" contains "session-state variable information", but it doesn't go into detail about what this information is or how to access it.
The page also says that the SessionState class can be used to access session-state data. This class contains a property called PSVariable, of type PSVariableIntrinsics.
However, I have two problems with this. The first problem is that accessing the SessionState property requires me to inherit from PSCmdlet instead of Cmdlet, and I'm not sure if I want to do that.
The second problem is that I can't figure out how to make the variable private. Here's the code that I'm trying:
const int TestVariableDefault = 10;
const string TestVariableName = "TestVariable";
int TestVariable
{
get
{
return (int)SessionState.PSVariable.GetValue(TestVariableName,
TestVariableDefault);
}
set
{
PSVariable testVariable = new PSVariable(TestVariableName, value,
ScopedItemOptions.Private);
SessionState.PSVariable.Set(testVariable);
}
}
The TestVariable property works just as I would expect. But despite the fact that I'm using ScopedItemOptions.Private, I can still access this variable at the prompt by typing in $TestVariable, and the variable is listed in the output of Get-Variable. I want my variable to be hidden from the user.
One approach would be to use a cmdlet or function that outputs a connection object. This object could be simply the PSCredential object, or it could contain the credential and other information like a connection string. You're saving this in a variable now and you can continue to do this, but you can also use $PSDefaultParamterValues to store this value and pass it to all the appropriate cmdlets in the module.
I've never written a C# module but I've done something similar in PS:
function Set-DefaultCredential
{
param
(
[PSCredential]
$Credential
)
$ModuleName = (Get-Item -Path $PSScriptRoot).Parent.Name
$Module = Get-Module -Name $ModuleName
$Commands = $Module.ExportedCommands.GetEnumerator() | Select-Object -ExpandProperty value | Select-Object -ExpandProperty name
foreach ($Command in $Commands)
{
$Global:PSDefaultParameterValues["$Command`:Credential"] = $Credential
}
}
This code sets the credential you've passed in as the default for any of the exported commands of my module using the $PSDefaultParameterValues automatic variable. Of course your logic may not be the same but it might show you the approach.
I've had similar thoughts, but never had the need to build a full and clean implementation. One approach to storing state that seemed appropriate for my connections was to encapsulate that state in a PSDrive. The such drives are integrated into session state.
The docs aren't always obvious on this, but it involves writing a class that derives from DriveCmdletProvider, which has built-in support for credential management. As I remember it, the NewDrive method can return a custom class derived from PSDriveInfo with additional members, including private or internal members, to store what you need.
You can then use New-PSDrive and Remove-PSDrive to establish/break connections connection with a drive name. Dynamic parameters provided by the DriveCmdletProvider let you customize parameters to New-PSDrive for your specifics.
See MSDN here for details.
I suppose the easiest way is to just make an advanced function in ps and use $script:mycred
But if u need to stick to pure c# and support multiple runspaces, it may be possible to make a dictionary of runspace ids and tokens
public static class TokenCollection {
public static readonly Dictionary<Guid,PSObject> Tokens = new Dictionary<Guid,PSObject>();
}
You then add your current runspace guid inside with your token
Guid runspId = Guid.Empty;
using (var runsp = PowerShell.Create(RunspaceMode.CurrentRunspace))
{
runspId = runsp.Runspace.InstanceId;
TokenCollection.Add(runspId, token);
}
Get a token like this
PSObject token = null;
if (TokenCollection.Tokens.ContainsKey(runspaceId))
{
token = TokenCollection.Tokens[runspId];
}

C# Environment variable value doesn't get added [duplicate]

This question already has an answer here:
How to set system environment variable in C#?
(1 answer)
Closed 7 years ago.
I'm trying to add a value to the Path environment variable, but I can't seem to get it to work. I've went through a couple of similar questions and I'm pretty sure I have exactly the same code, but still it won't add the variable or I can't see it. I've checked both the administrator and local user account for changes. I've checked both during and after the running (debugging) of the application. I've also close VS2013 completely and checked.
Here's the code I'm using
string path = #"C:\Users\bono\Documents\Visual Studio 2013\Projects\3D-Scanner\AddEnviromentToPath\bin\Debug\AddEnviromentToPath.exe";
ProcessStartInfo process_start_info = new ProcessStartInfo();
process_start_info.FileName = path;
process_start_info.Verb = "runas";
process_start_info.WindowStyle = ProcessWindowStyle.Normal;
process_start_info.UseShellExecute = true;
process_start_info.Arguments = PATH_TO_PCL;
Process.Start(process_start_info); //Process that handles the adding of the value
AddEnviromentToPath program:
class Program {
static void Main(string[] args) {
//Just to make sure we're adding both
AddToEnvironmentPath(args[0], EnvironmentVariableTarget.User);
AddToEnvironmentPath(args[0], EnvironmentVariableTarget.Machine);
}
static void AddToEnvironmentPath(string pathComponent, EnvironmentVariableTarget target) {
string targetPath = System.Environment.GetEnvironmentVariable("Path", target) ?? string.Empty;
if (!string.IsNullOrEmpty(targetPath) && !targetPath.EndsWith(";")) {
targetPath = targetPath + ';';
}
targetPath = targetPath + pathComponent;
Environment.SetEnvironmentVariable("Path", targetPath, target);
}
}
Note that I'm running VS2013 and the main application as a standard user. When the AddEnviromentToPath program runs I get an admin verification panel. I log in here with an admin account.
Edit:
Other people seem to get it working with basically the same code:
How do I get and set Environment variables in C#?
Environment is not being set in windows using c#. Where am I going wrong?
Assuming that Environment.SetEnvironmentVariable calls the Win32 SetEnvironmentVariable function behind the scenes, this note is probably applicable:
Sets the contents of the specified environment variable for the
current process
...
This function has no effect on the system
environment variables or the environment variables of other processes.
If you want to change a global environment variable and have existing processes notice it, you need to:
Save it to the registry under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
Notify existing processes of the change with the WM_SETTINGCHANGE message.
See the MSDN Environment Variables documentation for more information.
ProcessStartInfo to the rescue!
You need to check this documentation out:
https://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.environmentvariables%28v=vs.110%29.aspx
The key text which addresses your concerns:
Although you cannot set the EnvironmentVariables property, you can
modify the StringDictionary returned by the property.

Why is this environment variable always null?

I want to automatically get the directory: user\mydocuments
So I did:
t = Environment.GetEnvironmentVariable(Environment.GetFolderPath(Environment.SpecialFolder.Personal));
But t is null all the time.
The source of the problem is that you are calling Environment.GetEnvironmentVariable when you really don't need to.
Your code successfully obtains the directory path but then you proceeded pass said directory path to GetEnvironmentVariable() which in turn, proceeds to look at the system's environment variables for an environment variable called "user\my_documents". Because no such environment variable exists the function will return null.
Simply do not pass the directory path to GetEnvironmentVariable() and your code should function as expected:
var foo =
Environment.GetFolderPath(Environment.SpecialFolder.Personal);

Categories