Call Set-Location from within a cmdlet - c#

I'm creating a cmdlet for PowerShell and I need to be able to call Set-Location (aka cd) from within a the cmdlet. I would do it like this
var setLocation = new Microsoft.PowerShell.Commands.SetLocationCommand();
setLocation.Path = path;
setLocation.Invoke();
except that it gives me an error that says You cannot invoke a cmdlet that derrives from PSCmdlet. I'd like to use Set-Location, but I'd be happy with just simply changing the directory of the shell.

The best answer I could find was to use InvokeScript to change directory:
InvokeCommand.InvokeScript(string.Format("Set-Location {0}", fullPath));
It's possible that there's a "more C#" way to do this, but I couldn't find it.

The question is a bit older but here is a slightly nicer solution:
The class PathIntrinsics contains the method SetLocation.
To manipulate the path of the current session, you can access the PathIntrinsics via the SessionState using the Path property.
long story short:
SessionState.Path.SetLocation("C:/some/path");

You could try to derive from CMDLET not PSCMDLET.

Simply using Set-Location inside the script should do the work.
So for example:
# Script.ps1
CD ~
Get-Location
Set-Location c:\Windows
Get-location
Once the script finishes, try Get-Location again!

If I use Set-Location in a script, I usually like to first use Push-Location to store the current location and then at the end of my script I can use Pop-Location to return the user to where they were.
Push-Location $PWD
Set-Location $somewhere
#script body
Pop-Location
Unless of course, the intention of the script is to change directories for the user.

Related

Running a powershell script from C# ends in error

I want to trigger a site design from powershell. It works here but when I try to run it through c# I get this error:
Cannot convert value "param" to type
"Microsoft.Online.SharePoint.PowerShell.SPOSiteDesignPipeBind". Error:
"Guid should contain 32 digits with 4 dashes
(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
I have tested several versions of the addScript string but none have worked. This is the code I'm trying to run. And it works fine in powershell.
PowerShellInstance.AddScript("Invoke-SPOSiteDesign -Identity param($paramSiteDesignId) -WebUrl param($paramUrl)");
PowerShellInstance.AddParameter("paramSiteDesignId", "176d2af0-1772-41b2-9ad7-acfceefc8851");
PowerShellInstance.AddParameter("paramUrl", "https://TenantName.sharepoint.com/sites/TMVTest13");
Any help pointing me in the right direction is very appreciated.
I found a way doing it directly in C# with Tenant.ApplySiteDesign. It works great see link for more info https://laurakokkarinen.com/the-ultimate-guide-to-sharepoint-site-designs-and-site-scripts/#applying-site-designs-programmatically
As you can see on learn.microsoft.com, AddParameter doesn't work by replacing placeholders the way you are doing it, but instead works by adding parameter/value pairs to the given command as parameters.
Your code probably results in something like this:
Invoke-SPOSiteDesign -Identity param($paramSiteDesignId) -WebUrl param($paramUrl) -paramSiteDesignId 176d2af0-1772-41b2-9ad7-acfceefc8851 -paramUrl https://TenantName.sharepoint.com/sites/TMVTest13
According to the documentation, this should do what you want:
PowerShellInstance = PowerShellInstance.AddScript("Invoke-SPOSiteDesign");
PowerShellInstance = PowerShellInstance.AddParameter("Identity", "176d2af0-1772-41b2-9ad7-acfceefc8851");
PowerShellInstance = PowerShellInstance.AddParameter("WebUrl", "https://TenantName.sharepoint.com/sites/TMVTest13");```

How to access PowerShell host from C#

In a PowerShell profile, one can identify the PowerShell host in order to do appropriate setup for that host's environment. For example:
if ($host.Name -eq 'ConsoleHost')
{
Import-Module PSReadline
# differentiate verbose from warnings!
$privData = (Get-Host).PrivateData
$privData.VerboseForegroundColor = "cyan"
}
elseif ($host.Name -like '*ISE Host')
{
Start-Steroids
Import-Module PsIseProjectExplorer
}
I would like to be able to do the equivalent identification from a C# context primarily because PowerShell ISE does not support Console.ReadLine so I want to know if it is safe to use it in the current PS host's environment.
I first explored trying to get the output of the Get-Host cmdlet from within C# (per Invoking a cmdlet within a cmdlet). After I located the Microsoft.PowerShell.Commands.Utility assembly (under C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0) I could compile this but it yielded null...
var cmd = new Microsoft.PowerShell.Commands.GetHostCommand();
var myHost = cmd.Invoke();
...while this would not compile due to the InternalHost class being (ironically!) internal:
var cmd = new Microsoft.PowerShell.Commands.GetHostCommand();
var myHost = cmd.Invoke<System.Management.Automation.Internal.Host.InternalHost>();
Next, I then modified my cmdlet to inherit from PSCmdlet rather than Cmdlet (to allow access to the SessionState), so I could then access the PS host object like this:
var psVarObject = SessionState.PSVariable.GetValue("Host");
Of course, that returns a pure Object, which I then needed to cast to... oh, wait... it's still internal!... so this would not compile:
string psHost = ((System.Management.Automation.Internal.Host.InternalHost)psVarObject).Name;
Leaving me no alternative but to use reflection on a foreign assembly (horrors!):
string psHost = (string)psVarObject.GetType().GetProperty("Name").GetValue(psVarObject, null);
That works, but is less than ideal, because reflecting upon any 3rd-party assembly is a fragile thing to do.
Any alternative ideas on either (a) identifying the host or, (b) backing up a bit, being able to use the host's own Read-Host cmdlet to get a typed input from a user?
You can just use Host property from PSCmdlet class. And if you want to do Read-Host:
Host.UI.ReadLine()
When getting
var psVarObject = SessionState.PSVariable.GetValue("Host");
You can cast it to System.Management.Automation.Host.PSHost instead of InternalHost

Calling RubyScript via IronRuby with command line parameters or arguments

We're pretty new to Ruby and very new to IronRuby so please bear with me. We're in C# trying to do something very simple. I've got a ruby script called doExtract.rb and I need to pass it a file called myfile.txt. We've copied all the files required into the /bin folder of the build and they run correctly when called via the command line.
var rubyRuntime = Ruby.CreateRuntime();
var rubyEngine = rubyRuntime.GetEngine("rb");
String fullPath = String.Format("{0} {1}", "doExtract.rb", "myfile.txt");
rubyEngine.ExecuteFile(fullPath);
gives me an error of "Illegal characters in path"
I've searched high & low on the t'interwebs and to no avail.
We've tried adding the search paths to the rubyEngine and using a full path to the myfile.txt but still get the error. If we call a simple ruby script with no parameters then it works fine. We've also tried with escaped slashed both backwards and forwards in the myfile.txt. I'm sure it'd something really stupid that we're not doing !
Any suggestions where we're going wrong ?
Thanks

Escaping single quotes in C# powershell runspace

I am trying to run Exchange cmdlets using System.Automation dll in C#.
In http://technet.microsoft.com/en-us/library/dd315325.aspx, they have said that for escaping single quotes, we basically need to append it with another single quote
For example,
[PS] C:\Windows\system32>Write-Host 'Live and let le''arn'
Live and let le'arn
However, when I try to do the same thing with my cmdlet,
New-Mailbox -Name 'user1.''.cn'
The new mailbox is actually created with name as user.''.cn. We would like it to be user.'.cn
Code to execute this cmdlet is as follows:
AutomatedRunspace.Command command = new AutomatedRunspace.Command(cmdlet.Command);
foreach (CmdletParameter param in cmdlet.GetParameters())
{
command.Parameters.Add(param.Name, param.Value);
}
pipeline.Commands.Add(command);
Is there anything we can do to correctly escape it?
When you're invoking cmdlets in C#, you need to also worry about the C# string quoting behavior. It's not clear in your question what exactly you're setting the Name parameter to in C# code. I would try this:
string nameArg = "user1.'.cn";
That is, the PowerShell API should be bypassing the parameter parsing phase since you're supplying the argument directly via the API.

Changing environment variables of the calling process

This one seems trivial but the answer has eluded me for a few days now.
I have a Windows batch file, that calls a C# program to do an extra verification that cannot be done in a batch file. After the verification is complete I need to return a status and a string back to the calling shell.
Now the return value is trivial and my C# console app simply sets a return value (exit code if you will). And I thought the string will also be a piece of cake. I attempted to define a new shell variable using the:
Environment.SetEnvironmentVariable("ERR", "Some text");
This call should (and does) define a shell variable within the current process - that is the very C# process that created the variable. The value is lost as soon as the C# app terminates and the shell that created the C# app knows nothing about the variable. So... A call with no particular use... At all... Unless perhaps if I created a child process from the C3 app, perhaps it would inherit my variables.
The EnvironmentVariableTarget.Machine and EnvironmentVariableTarget.User targets for the SetEnvironmentVariable call don't solve the problem either, as only a newly created process will get these new values from the registry.
So the only working solution I can think of is:
write to stdout
write to a file
encode extra meaning into the return value
The first two are a bit ugly and the last one has its limitations and problems.
Any other ideas (how to set a shell variable in the parent process)? Maybe such shell variable modifications are a security concern (think PATH)...
Thank-you for your time.
I had the same problem as Ryan and the only thing that came to my mind as a work-around was to write a batch in error out to set the variable and to call it from the batch.
ConsoleApplication1.exe:
'put some sensible code here
'put result in variable myResult
Dim myResult As String = Guid.NewGuid().ToString("D").ToUpperInvariant()
Console.WriteLine("Normal output from the consonle app")
Console.Error.WriteLine("#ECHO OFF")
Console.Error.WriteLine("SET zzzResult={0}", myResult)
Test.cmd (the calling batch):
#ECHO OFF
:Jump to folder of batch file
PUSHD %~d0%~p0
:Define a temp file
SET zzzTempFile=%TEMP%\TMP%Random%.CMD
:Call .NET console app
ConsoleApplication1.exe 2>%zzzTempFile%
:Call the generated batch file
CALL %zzzTempFile%
:Clean up temp file
DEL %zzzTempFile%
:Clean up variable
SET zzzTempFile=
:Do something with the result
ECHO Yeah, we finally got it!
ECHO:
ECHO The value is "%zzzResult%".
ECHO:
:Clean up result variable
SET zzzResult=
:Go back to original folder
POPD
That should do the trick. And yes, I do know this is an old post and Ryan is solving other issues by now, but there might be still somebody else out there having the same problem...
What you are asking is to be able to arbitrarily write to the memory space of a running process. For good reason, this is not possible without SeDebugPrivilege.
Any of the three solutions you list will work. Stdout is the standard way to communicate with a batch script.
By the way, you're writing a Windows batch file. I'm pretty sure the ship has already sailed on "a bit ugly".
If you want to put a value of some output into a variable in the batch you can use the following construct:
FOR /F "usebackq tokens=4 delims=\[\] " %i IN (`ver`) DO set VERSION=%i
ECHO %VERSION%
Output on my OS:
6.1.7601
'usebackq' means we are using back quotes which gives the ability to use a fileset in the command quoted with double quotes. You may not need this. 'tokens' means the index in the resulting string array to select (it can be a range M-N). If you need to skip lines use 'skip=X'). 'delims' are the string separators to use (like string-Split() in .Net).
You will put your console app instead of 'ver' and adapt the delimiters and tokens to match your specific output. If you have more variables to fill you will need to make the if a bit more complex but that should make a good start.
My BAT is a bit rusty, but I think it's possible to retrieve the 'exit' code from processes you've run externally, perhaps via %ERRORLEVEL%. If that's the case, make sure to exit your program via
Environment.Exit(123); // where 123 = error code
You can't add any messages, so you'll have to do that in the .bat file.
If this isn't the case, stdout is probably the best way.
After stumbling on this myself as well recently, I came up with this approach. What I did is run the bat file using the Process class, i.e.
// Spawn your process as you normally would... but also have it dump the environment varaibles
Process process = new Process();
process.StartInfo.FileName = mybatfile.bat;
process.StartInfo.Arguments = #"&&set>>envirodump.txt";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = false;
process.Start();
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
// Read the environment variable lines into a string array
string[] envirolines = File.ReadAllLines("envirodump.txt");
File.Delete("envirodump.txt");
// Now simply set the environment variables in the parent process
foreach(string line in a)
{
string var = line.Split('=')[0];
string val = line.Split('=')[1];
Environment.SetEnvironmentVariable(var, val);
}
This seems to have worked for me. It's not the cleanest approach, but will work in a bind. :)

Categories

Resources