Escaping single quotes in C# powershell runspace - c#

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.

Related

Cannot process command because of one or more missing mandatory parameters: Identity

I'm getting a, possibly misleading, error when i try to execute powershell from my c# app using powershell.
The error, as in the title, suggests that i'm missing the Identity parameter, but it isn't missing.
I tried debugging through, and confirming that the parameter is added to the Command object, before invoking.
var x = ps.AddScript("Remove-CsOnlineVoiceRoutingPolicy")
.AddParameter("Identity", "DK")
.AddParameter("-Force");
x.Invoke();
I'm running powershell 7.2, and using System.Management.Automation.Powershell version 7.2.1.0
Any ideas as to why this happens ?
I've tried both parameters with and without the dash, making no difference.
The error was using AddScript in addition with addParameter.
When using add scripts the params should be inline in the script, if you want to use addPArameter, it should be following the AddCommand.
dashes in the parameter name in AddParameter() seems to be completely ignored.
a working example would look like this.
var x = ps.AddCommand"Remove-CsOnlineVoiceRoutingPolicy")
.AddParameter("Identity", "DK")
.AddParameter("Force");
x.Invoke();

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");```

Call PS Script, Save String Returned

My PS script returns a string.
Function GetData {
Param(
[string]$id
)
Process
{
return "Value is $id"
}
GetData -id $arg
The below is the C# that calls the PS script:
PowerShell ps = PowerShell.Create();
string psScript = "GetData.ps1";
ps.AddScript(psScript);
// only takes one parameter
ps.AddParameter("25");
Collection<PSObject> results = ps.Invoke();
foreach (PSObject r in results)
{
Console.WriteLine(r.ToString());
}
Console.ReadLine();
Nothing returns.
I double checked the script and it does return a value when I pass in the path manually when calling the script directly in PowerShell. I also made sure that in the Properties of the project the Platform target is x64 (based on another question's error). I also tried to directly save the result in the Invoke method, but it gave an error, which showed that I have to actually save it in a collection, even though it's one record.
Forgot, also tried:
psParam = "25";
string psScript = "GetData.ps1 -arg'" + psParam + "'";
And no result on the console.
Tested this:
PowerShell ps = PowerShell.Create();
string psScript = ".\\GetData.ps1";
ps.AddCommand(psScript);
ps.AddArgument("25");
Collection<PSObject> results = ps.Invoke();
foreach (PSObject r in results)
{
Console.WriteLine(r.ToString());
}
Console.ReadLine();
And used most of the above and this errors because it says GetData.ps1 is not recognized as the name of a cmdlet, function, script file, or operable program. If I point directly to it by placing it on my C drive (C:\GetData.ps1), it does nothing.
Double check; inside the script I am calling the function on the last line:
GetData -id $arg
Is this correct?
Pay attention to your function. It gets an open curly brace '{', but not the matching close curly brace '}'. Your defective PowerShell code will emit an error not caught by your code.
After you correct this simple error, notice how you're calling your function. What is $arg? I assure you it's not any automatic variable. Have a look in about_automatic_variables...
P.S.: you'd better off asking enormously difficult questions like this one in social . technet . microsoft . com / Forums / windowsserver / en-US / home?forum=winserverpowershell . If you ha did it, the answer would have been posted many hours ago.
The problem is that "return" is not what you think.
In Powershell, the "return value" is the last value on the stack when execution ends. In your case, just omit the "return" keyword, and the string will come out as you expect.
Function return value in PowerShell
Alternatively, you can use Write-Output which would explicitly send the data to the output like a C-style return statement.
Note: Do NOT use Write-Host, as it writes directly to the powershell host, skipping the pipeline and never giving you a chance to see the value.

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

Run powershell from c# most efficient way to create multiple mailcontacts in Exchange Online

I wish to create multiple mailcontacts (external Contacts) in the GAL in Microsoft Online by running Powershell command from C#. The code below works, but is very slow and takes about 15-20 min to run for 400 mailcontacts.
foreach(EmailAdressVM emailAddressVM in emailList.emailAddresses1)
{
//Create New MailContact.
Pipeline pplNewMailContact = runspace.CreatePipeline();
Command cmdNewMailContact = new Command("New-MailContact");
cmdNewMailContact.Parameters.Add("Name", emailAddressVM.sExternalEmailAddress);
cmdNewMailContact.Parameters.Add("Displayname", emailAddressVM.sFullName.Trim());
cmdNewMailContact.Parameters.Add("Lastname", emailAddressVM.sLastName.Trim());
cmdNewMailContact.Parameters.Add("Firstname", emailAddressVM.sFirstName.Trim());
cmdNewMailContact.Parameters.Add("ExternalEmailAddress", emailAddressVM.sExternalEmailAddress.Trim());
pplNewMailContact.Commands.Add(cmdNewMailContact);
pplNewMailContact.Invoke();
pplNewMailContact.Stop();
pplNewMailContact.Dispose();
}
I am guessing that this is slow since I create a new Pipeline for every new mailcontact that is added and there has to be a more eficient way of doing this since running...
import-csv <filename> | ForEach {
new-mailcontact -name $_.emailaddress -displayname $_.FullName -lastname $_.lastname -firstname $_.firstname -externalemailaddress $_.emailaddress -alias $_.alias
}
...is much faster.
I have found some references after many hours of searching the web that you can do something similar to using a CSV when running Powershell commands from C#, i.e. send a list (or array) of values to a command (in this case the "new-mailcontact" command). But, I have not found any good example of how to send more than one value to a command and I need to supply many values (for example: -name $.emailAddress -displayname $.FullName, etc.) to the "new-mailcontact" command.
Is it possible to send a list (or array) in a similar way as the "import-csv" command (when using regular powershell) and will this be faster, or is there an evan better way? Would I get better performance if I use Powershell 3 instead of 1 (as I am using now).
Please provide working sample code i C#!
Please note that I cannot save a CSV file to disk and the execute powershell from CMD since I do not have write access to disk and that I do not think that I can run an entire script remotely (since remote scripting probably is disabled on Exchange Online).
The biggest reason I would think is because for each address you are creating a new Powershell instance and you are not multithreaded.
You code looks something like this from above:
Foreach email address{
Declare a new Powershell process
Add attributes to call later
Start Powershell and pipe stuff in
Close Powershell instance
}
I think you would be better off creating the Powershell instance / pipe once and then sending each object into it. More along the lines of:
Create PS Pipe
Foreach email address{
PS.SendArguments(Email, Name, DN, etc.);
}
I am not in an environment to get something working or tested right now, so hopefully this gives you at least most of what you need...

Categories

Resources