I have to add multithreading capabilities to a PowerShell script. Unfortunately I get an error when I try to open an runspace pool using custom C# classes in the InitialSessionState object. The following example is a DEMO code only, not the original script. You can copy&paste it and it will run without any modifications needed. What is really strange is, that all seems to work correctly despite the error message coming up when opening the runspace pool.
This is the error message (translated):
Error while loading the extended type data file: Error in type data "FooBar.BarClass": The TypeData must have: "Members", "TypeConverters", "TypeAdapters" or "StandardMembers".
I have already worked and tested for hours and have no idea what is the reason for that message. And yes, I know that there are very good libraries and cmdlets available for multithreading, but I cannot use them for different reasons.
# Simple c-sharp classes
Add-Type -TypeDef #"
namespace FooBar {
public class FooClass {
public string Foo() {
return "Foo";
}
}
public class BarClass {
public string Bar() {
return "Bar";
}
}
}
"#
function callFooBar {
[FooBar.FooClass]$foo = New-Object FooBar.FooClass
[FooBar.BarClass]$bar = New-Object FooBar.BarClass
$foo.Foo() + $bar.Bar()
}
$scriptBlock = {
Write-Output ( callFooBar )
}
# Setting up an initial session state object
$initialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
# Getting the function definition for the functions to add
$functionDefinition = Get-Content function:\callFooBar
$functionEntry = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList 'callFooBar', $functionDefinition
# And add it to the iss object
[void]$initialSessionState.Commands.Add($functionEntry)
# Get the type data for the custom types to add
$typeData = New-Object System.Management.Automation.Runspaces.TypeData -ArgumentList 'FooBar.FooClass'
$typeEntry = New-Object System.Management.Automation.Runspaces.SessionStateTypeEntry -ArgumentList $typeData, $false
# And add it to the iss object
[void]$initialSessionState.Types.Add($typeEntry)
# Get the type data for the custom types to add
$typeData = New-Object System.Management.Automation.Runspaces.TypeData -ArgumentList 'FooBar.BarClass'
$typeEntry = New-Object System.Management.Automation.Runspaces.SessionStateTypeEntry -ArgumentList $typeData, $false
# And add it to the iss object
[void]$initialSessionState.Types.Add($typeEntry)
# Create Runspace pool
$RunspacePool = [RunspaceFactory]::CreateRunspacePool(1, ([int]$env:NUMBER_OF_PROCESSORS + 1), $initialSessionState, $Host)
$RunspacePool.ApartmentState = 'MTA'
[void]$RunspacePool.Open() # <<<< Error occurs here!
[System.Collections.Generic.List[object]]$Jobs = #()
1..2 | % {
$job = [System.Management.Automation.PowerShell]::Create($initialSessionState)
$job.RunspacePool = $RunspacePool
[void]$job.AddScript($scriptBlock)
$jobs += New-Object PSObject -Property #{
RunNum = $_
Pipe = $job
Result = $job.BeginInvoke()
}
}
do {
} while ($jobs.Result.IsCompleted -contains $false)
Write-Host "All jobs completed!"
$Results = #()
foreach ($job in $jobs) {
$Results += $job.Pipe.EndInvoke($job.Result)
}
$Results
$RunspacePool.Close()
$RunspacePool.Dispose()
#mklement0 Your hint leads me to the right direction! Custom C# classes with type-definition must be added in a different way to the runspacepool. This was working now (after a few hours testing with all possible runspace methods...):
# Get the script data for the custom c# class to add
$scriptDefinition = New-Object System.Management.Automation.Runspaces.ScriptConfigurationEntry -ArgumentList 'FooBar.FooClass', $false
$scriptEntry = New-Object System.Management.Automation.Runspaces.SessionStateScriptEntry -ArgumentList $scriptDefinition
# And add it to the iss object
[void]$initialSessionState.Commands.Add($scriptEntry)
# Get the script data for the custom c# class to add
$scriptDefinition = New-Object System.Management.Automation.Runspaces.ScriptConfigurationEntry -ArgumentList 'FooBar.BarClass', $false
$scriptEntry = New-Object System.Management.Automation.Runspaces.SessionStateScriptEntry -ArgumentList $scriptDefinition
# And add it to the iss object
[void]$initialSessionState.Commands.Add($scriptEntry)
Not by type, but with script and command. Thank you for your hint.
Related
I have an application, that allows the user to configure basic WMI settings on a Win 10 IoT machine.
I am currently struggling with reading all WEKF_PredefinedKey settings, that are enabled.
I am simply running a skript, that I added as string to the project settings named ReadEnabledKeys:
$CommonParams = #{"namespace"="root\standardcimv2\embedded"}
$CommonParams += $PSBoundParameters
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned;
$keys = Get-WMIObject -class WEKF_PredefinedKey #CommonParams
foreach($k in $keys)
{
if($k.Enabled -eq $false)
{
"$k";
}
}
My call in C# code looks like this (Note: using System.Management.Automation):
using (PowerShell PowerShellInstance = PowerShell.Create())
{
PowerShellInstance.AddScript(Properties.Settings.Default.ReadEnabledKeys);
var result = PowerShellInstance.Invoke();
}
My variable result will always stay empty.
If I run the skript in Powershell directly, the output is just fine (all shortcuts, that are currently not disabled).
I have something similar programmed with the unified write filter, where I enable and disable it:
$COMPUTER = "localhost"
$NAMESPACE = "root\standardcimv2\embedded"
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned;
$objUWFInstance = Get-WMIObject -namespace $NAMESPACE -class UWF_Filter;
$retval = $objUWFInstance.Enable();
if ($retval.ReturnValue -eq 0) {"Unified Write Filter will be enabled after the next system restart."}
else {"Unknown Error: " + "{0:x0}" -f $retval.ReturnValue}
And the C# call:
using (PowerShell PowerShellInstance = PowerShell.Create())
{
PowerShellInstance.AddScript(Properties.Settings.Default.EnableUWF);
// [0] = result or error
var result = PowerShellInstance.Invoke();
if (result[0].ToString().ToLower().Contains("enabled"))
MessageBox.Show(result[0].ToString(), "", MessageBoxButton.OK, MessageBoxImage.Information);
else
MessageBox.Show("Error when enabling the filter! " + Environment.NewLine + result[0].ToString(), "",
MessageBoxButton.OK, MessageBoxImage.Error);
}
Here my result variable will be filled with the expected strings.
I have tried Write-Host $k, as I suspected something wrong with the stream, but this was without any success.
The output in Powershell looks like this:
PS C:\Users\Administrator> C:\Users\Administrator\Desktop\Newfolder\Untitled1.ps1
\\DESKTOP-RMGOBMG\root\standardcimv2\embedded:WEKF_PredefinedKey.Id="Alt"\\DESKTOP-RMGOBMG\root\standardcimv2\embedded:WEKF_PredefinedKey.Id="Application"
\\DESKTOP-RMGOBMG\root\standardcimv2\embedded:WEKF_PredefinedKey.Id="Ctrl+Esc"
\\DESKTOP-RMGOBMG\root\standardcimv2\embedded:WEKF_PredefinedKey.Id="Ctrl+F4"
\\DESKTOP-RMGOBMG\root\standardcimv2\embedded:WEKF_PredefinedKey.Id="Ctrl+Tab"
.
.
.
Can anyone tell me, what the problem is?
The problem appears to be with your script. Setting the ExecutionPolicy midstream doesn't do anything and you aren't writing a function so adding $PSBoundParameters also doesn't do anything. Here's an example that should work (I'd specify PS version in the future. I know you're on v5.1/win10 due to keyboard filtering)
$collection = [System.Collections.Generic.List[string]]::new()
foreach ($key in (Get-CimInstance -Namespace 'root\standardcimv2\embedded' -ClassName WEKF_PredefinedKey)) {
if (-not $key.Enabled) {
$collection.Add($key.ToString())
}
}
return $collection
(simplified)
#(Get-CimInstance -Namespace root\standardcimv2\embedded -ClassName WEKF_PredefinedKey).
Where{-not $_.Enabled}.
ForEach('ToString')
Example:
using (PowerShell ps = PowerShell.Create())
{
string script = #"Import-Module -Name C:\Windows\system32\WindowsPowerShell\v1.0\Modules\Microsoft.PowerShell.Management\Microsoft.PowerShell.Management.psd1 -ErrorAction Stop; #(Get-WmiObject -Namespace root\standardcimv2\embedded -Class WEKF_PredefinedKey -ErrorAction Stop).Where{-not $_.Enabled}.ForEach('ToString')";
ps.AddScript(script);
var result = ps.Invoke();
}
I am working on a c# program using System.Speech.Recognition to recognize speech and run PowerShell commands according to what is said.
I have the following powershell script that represents a macro for creating a speech command:
Add-Type -Path ".\GAVPI.Lib.dll"
Add-Type -Path ".\GAVPI.Lib.Logging.dll"
[Action[GAVPI.Lib.Logging.Parameter]]$speechRecognized = {
param($i)
[System.Windows.MessageBox]::Show("test")
}
$parameter = New-Object -TypeName GAVPI.Lib.Logging.Parameter -ArgumentList
("parameter", "value")
$phrase = New-Object -TypeName GAVPI.Lib.Core.Triggers.Phrase -ArgumentList
("default","test", $speechRecognized, $parameter)
return $phrase
This phrase object is used to tell what commands can be said and recognized. It successfully is passed to c# like this:
var list = new List<Phrase>();
var ps = PowerShell.Create();
var run = RunspaceFactory.CreateRunspace();
ps.Runspace = run;
run.Open();
var script = ps.AddScript(".\\PowershellTemplate.ps1", true);
var result = ps.Invoke();
foreach (var psObject in result)
{
if (psObject.BaseObject is Phrase)
{
list.Add((Phrase)psObject.BaseObject);
}
}
return list;
When a command is recognized,the Phrase class invokes the Action:
public override void Run(Parameter selectedparameter)
{
if (parAction != null)
{
parAction.Invoke(selectedparameter);
}
}
private Action<Parameter> parAction;
When the program is run, if you say "test parameter" it invokes the action<parameter> in the phrase class, which invokes the script block in powershell.
I get the following exception at the parAction.Invoke(selectedparameter); line:
There is no Runspace available to run scripts in this thread. You can
provide one in the DefaultRunspace property of the
System.Management.Automation.Runspaces.Runspace type. The script block
you attempted to invoke was:
param($i)...w("test")
How do I pass the runspace to the Sysem.Action<Parameter> so that it can run the PowerShell script block?
I have a .NET 4.5 web app (ASP.NET MVC 4 Web Application) and my problem seems to be that running powershell code with some simple azure storage functionality will not work when it is executed from C#. But it will work when executed through the normal powershell console or the ISE.
Powershell (Just creates a table and inserts a column for testing):
function Test-SetTableValues($storageAccount, $storageKey, $tableName, $valuesHash)
{
$storageContext = New-AzureStorageContext $storageAccount -StorageAccountKey $storageKey
$table = New-AzureStorageTable –Name $tableName –Context $storageContext
$entity = New-Object Microsoft.WindowsAzure.Storage.Table.DynamicTableEntity "PartitionKey", "RowKey"
foreach ($key in $valuesHash.Keys)
{
$entity.Properties.Add($key, $valuesHash[$key])
}
$result = $table.CloudTable.Execute([Microsoft.WindowsAzure.Storage.Table.TableOperation]::InsertOrMerge($entity), $null, $null)
}
Test-SetTableValues("STORAGE_ACCOUNT", "STORAGE_KEY", "TABLE_NAME", #{MyColumn="MyValue"})
It doesn't work when executed through C#:
using System.Management.Automation;
using System.Management.Automation.Runspaces;
public static void Test()
{
var runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
var powershell = PowerShell.Create();
powershell.Runspace = runspace;
powershell.AddScript(/*powershell code here*/);
powershell.AddCommand("out-default");
powershell.Invoke();
}
And the reason is because the library Microsoft.WindowsAzure.Storage is included.
If we remove it, the code will work. But I need it for other things.
The CLR Version is the same for all environments(c#/ise/powershell_console) as I tried outputing the variable $PSVersionTable (CLRVersion 4.0.30319.17400).
The error we get is this:
Cannot convert argument "operation", with value:
"Microsoft.WindowsAzure.Storag e.Table.TableOperation", for "Execute"
to type "Microsoft.WindowsAzure.Storage. Table.TableOperation":
"Cannot convert the "Microsoft.WindowsAzure.Storage.Tabl
e.TableOperation" value of type
"Microsoft.WindowsAzure.Storage.Table.TableOper ation" to type
"Microsoft.WindowsAzure.Storage.Table.TableOperation"." At
OurPowershellFile.ps1:90 char:5
+ $result = $table.CloudTable.Execute([Microsoft.WindowsAzure.Storage.Table .Ta
...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
Thanks in advance for any answers. I just can't find a solution for this!
Thanks to microsoft support is seems like modifying the powershell code slightly "fixes" the issue. Not sure why exactly but the below code works.
function Test-SetTableValues($storageAccount, $storageKey, $tableName, $valuesHash)
{
$accountCredentials = New-Object "Microsoft.WindowsAzure.Storage.Auth.StorageCredentials" $storageAccount, $storageKey
$storageAccount = New-Object "Microsoft.WindowsAzure.Storage.CloudStorageAccount" $accountCredentials, $true
$tableClient = $storageAccount.CreateCloudTableClient()
$table = $tableClient.GetTableReference($tableName)
$table.CreateIfNotExists()
$entity = New-Object Microsoft.WindowsAzure.Storage.Table.DynamicTableEntity "PartitionKey", "RowKey"
foreach ($key in $valuesHash.Keys)
{
$entity.Properties.Add($key, $valuesHash[$key])
}
$result = $table.Execute([Microsoft.WindowsAzure.Storage.Table.TableOperation]::InsertOrMerge($entity))
}
Test-SetTableValues("STORAGE_ACCOUNT", "STORAGE_KEY", "TABLE_NAME", #{MyColumn="MyValue"})
I am trying to call\create runspace within a class that derived from PSCmdlet. Since PSCmdlet includes a default session state that contains shared data I want to reuse in the runspace, I am wondering if there is a programmatically way to convert the current sessionState into the runspace's InitialSessionState ?
If there is no such way, I am not really understand why such session state info cannot be shared within different runspace. This looks like running a remote runspace to me. Can anyone explain?
For example,
namespace CustomCmdlet.Test
{
[Cmdlet("Get", "Test1")]
public class GetTest1 : PSCmdlet
{
protected override void ProcessRecord()
{ WriteObject("1"); }
}
[Cmdlet("Get", "Test2")]
public class GetTest2 : PSCmdlet
{
protected override void ProcessRecord()
{
// instead of import the module dll using Runspace
// InitialSessionState.ImportModule(new string[] {"CustomCmdlet.Test.dll"});
// Runspace runspace = RunspaceFactory.CreateRunspace(InitialSessionState)
// is it any way to import the PSCmdlet.SessionState into the InitialSessionState?
}
}
We are using PowerShell 4.0, if this is relevant.
Session state definitely can't be shared across runspaces, it holds session specific data like variables.
The InitialSessionState that was used to create a runspace is a property of the runspace. You can access the current runspace via thread local storage using the Runspace.DefaultRunspace property: http://msdn.microsoft.com/en-us/library/system.management.automation.runspaces.runspace.defaultrunspace(v=vs.85).aspx
That said, you may want to look at RunspacePool - the pool of runspaces will all be created from the same InitialSessionState. This way you avoid creating more runspaces than necessary, instead just reusing a runspace from the pool.
I know this is years late, but
$maxThreads = 17;
$iss = [system.management.automation.runspaces.initialsessionstate]::CreateDefault();
[void]$iss.ImportPSModule($exchangeModule.Path)
$runspacePool = [runspacefactory]::CreateRunspacePool(1, $maxThreads, $iss, $host)
$runspacePool.Open() > $null;
#EndRegion Initial sessionstate
#Region Run threads and collect data
$threads = New-Object System.Collections.ArrayList;
foreach ($server in $servers)
{
if (!($server.ToString().Contains("unreachable")))
{
$powerShell = [powershell]::Create($iss);
$powerShell.RunspacePool = $runspacePool;
[void]$powerShell.AddScript({
Param ($server)
[pscustomobject]#{
server = $server
} | Out-Null
$global:ProgressPreference = 'SilentlyContinue';
$returnVal = Get-Queue -Server $server; # | where {$_.MessageCount -gt 1};
$customObject = New-Object -TypeName psobject;
$customObject | Add-Member -MemberType NoteProperty -Name ServerName -Value $server;
$customObject | Add-Member -MemberType NoteProperty -Name MessageCountObject -Value $returnVal;
#Write-Host "Object return value is is: " $messageCountObject;
return $customObject;
}) # end of powershell.Add script
$powerShell.AddParameter('server', $server) | Out-Null;
$returnVal = $PowerShell.BeginInvoke();
$temp = "" | Select PowerShell,returnVal;
$temp.PowerShell = $PowerShell;
$temp.returnVal = $returnVal;
$threads.Add($Temp) | Out-Null;
}
else
{
$threads.Add($server) | Out-Null;
}
} #foreach server
#EndRegion Run threads and collect data
In Exchange 2007 this line of code is used to load the Exchange Poweshell commands snapin:
PSSnapInInfo info = rsConfig.AddPSSnapIn(
"Microsoft.Exchange.Management.PowerShell.Admin",
out snapInException);
However, this does not exist in Exchange 2010 and I am pulling my hair out trying to find out how to access the Exchange Powershell commands from C# code. Microsoft.Exchange.Management.PowerShell.Admin does not exist anywhere on the Exchange Server and I can find nothing on Google that talks about an equivalent line of code.
How do I access Exchange Management Tools from C# code in Exchange 2010?
Below is my complete code for reference, it all works until I add the line of code:
//Creating and Opening a Runspace
RunspaceConfiguration rsConfig = RunspaceConfiguration.Create();
PSSnapInException snapInException = null;
PSSnapInInfo info = rsConfig.AddPSSnapIn(
"Microsoft.Exchange.Management.PowerShell.Admin",
out snapInException);
Runspace myRunSpace = RunspaceFactory.CreateRunspace(rsConfig);
myRunSpace.Open();
//How Do I Run a Cmdlet?
//create a new instance of the Pipeline class
Pipeline pipeLine = myRunSpace.CreatePipeline();
//create an instance of the Command class
// by using the name of the cmdlet that you want to run
Command myCommand = new Command(txtCommand.Text);
//add the command to the Commands collection of the pipeline
pipeLine.Commands.Add(myCommand);
Collection<PSObject> commandResults = pipeLine.Invoke();
// iterate through the commandResults collection
// and get the name of each cmdlet
txtResult.Text = "start ....";
foreach (PSObject cmdlet in commandResults)
{
string cmdletName = cmdlet.Properties["Name"].Value.ToString();
System.Diagnostics.Debug.Print(cmdletName);
txtResult.Text += "cmdletName: " + cmdletName;
}
txtResult.Text += ".... end";
I don't know for sure, but Exchange 2010 powershell might be implemented as a powershell 2.0 module, which is loaded in a different manner. To find out, go to a system with the exchange management shell on it, and fire it up. Next, run:
ps> get-module
This will list the loaded v2 modules. I would expect the exchange one to appear if you have started the dedicated exchange management shell. If you loaded the regular powershell console, try:
ps> get-module -list
This will list all modules available to load. If you spot the right one, then you'll need to build your code against the v2 system.management.automation dll. For reasons beyond the scope of this reply, v2 powershell's assembly has the same strong name as v1's, so you cannot easily have both versions of powershell on the same machine. Build this from a machine with v2 powershell installed:
InitialSessionState initial = InitialSessionState.CreateDefault();
initialSession.ImportPSModule(new[] { *modulePathOrModuleName* });
Runspace runspace = RunspaceFactory.CreateRunspace(initial);
runspace.Open();
RunspaceInvoke invoker = new RunspaceInvoke(runspace);
Collection<PSObject> results = invoker.Invoke(*myScript*);
Hope this helps,
-Oisin
After a lot of trial and error, I finally figured this out. The problem with the code above is it works great when run against Exchange 2007 but things have changed in Exchange 2010. Instead of the snapin called "Microsoft.Exchange.Management.PowerShell.Admin", use this snapin, "Microsoft.Exchange.Management.PowerShell.E2010".
The complete code to run a Powershell command from C# looks like this. Hope this helps someone else trying to do this.
You will need references to System.Management.Automation.Runspaces, System.Collections.ObjectModel and System.Management.Automation also.
I found that the reference to System.Management.Automation had to be manually added to the csproj file itself in the ItemGroup section using notepad like this:
<Reference Include="System.Management.Automation" />
code below:
private class z_test
{
//set up
private RunspaceConfiguration rsConfig = RunspaceConfiguration.Create();
private PSSnapInException snapInException = null;
private Runspace runSpace;
private void RunPowerShell()
{
//create the runspace
runSpace = RunspaceFactory.CreateRunspace(rsConfig);
runSpace.Open();
rsConfig.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.E2010", out snapInException);
//set up the pipeline to run the powershell command
Pipeline pipeLine = runSpace.CreatePipeline();
//create the script to run
String sScript = "get-mailbox -identity 'rj'";
//invoke the command
pipeLine.Commands.AddScript(sScript);
Collection<PSObject> commandResults = pipeLine.Invoke();
//loop through the results of the command and load the SamAccountName into the list
foreach (PSObject results in commandResults)
{
Console.WriteLine(results.Properties["SamAccountName"].Value.ToString());
}
pipeLine.Dispose();
runSpace.Close();
}
}
This is what I am doing:
$sessionOptionsTimeout=180000
$sessionOptionsTimeout=180000
$so = New-PSSessionOption -OperationTimeout $sessionOptionsTimeout -IdleTimeout $sessionOptionsTimeout -OpenTimeout $sessionOptionsTimeout
$connectionUri="http://$fqdn/powershell?serializationLevel=Full;ExchClientVer=14.3.91.1"
$s = New-PSSession -ConnectionURI "$connectionUri" -ConfigurationName Microsoft.Exchange -SessionOption $so
$s | Enter-PSSession
PS>get-mailboxserver
EncryptionRequired AutoDatabaseMountDial DatabaseCopyAutoActivationPo
licy
------------------ --------------------- ----------------------------
e GoodAvailability Unrestricted
e GoodAvailability Unrestricted
Now, converting above to .net (c#) should be easy...
Essentially an exerpt from: "C:\Program Files\Microsoft\Exchange Server\V14\Bin\ConnectFunctions.ps1"
Please refer to the following function:
function _NewExchangeRunspace([String]$fqdn,
[System.Management.Automation.PSCredential]
$credential=$null,
[bool]$UseWIA=$true,
[bool]$SuppressError=$false,
$ClientApplication=$null,
$AllowRedirection=$false)
{
$hostFQDN = _GetHostFqdn
if (($fqdn -ne $null) -and ($hostFQDN -ne $null) -and ($hostFQDN.ToLower() -eq $fqdn.ToLower()))
{
$ServicesRunning = _CheckServicesStarted
if ($ServicesRunning -eq $false)
{
return
}
}
Write-Verbose ($ConnectFunctions_LocalizedStrings.res_0005 -f $fqdn)
$so = New-PSSessionOption -OperationTimeout $sessionOptionsTimeout -IdleTimeout $sessionOptionsTimeout -OpenTimeout $sessionOptionsTimeout;
$setupRegistryEntry = get-itemproperty HKLM:\SOFTWARE\Microsoft\ExchangeServer\v14\Setup -erroraction:silentlycontinue
if ( $setupRegistryEntry -ne $null)
{
$clientVersion = "{0}.{1}.{2}.{3}" -f $setupRegistryEntry.MsiProductMajor, $setupRegistryEntry.MsiProductMinor, $setupRegistryEntry.MsiBuildMajor, $setupRegistryEntry.MsiBuildMinor
$connectionUri = "http://$fqdn/powershell?serializationLevel=Full;ExchClientVer=$clientVersion"
}
else
{
$connectionUri = "http://$fqdn/powershell?serializationLevel=Full"
}
if ($ClientApplication -ne $null)
{
$connectionUri = $connectionUri + ";clientApplication=$ClientApplication"
}
write-host -fore Yellow ("connectionUri: " + $connectionUri)
$contents = 'New-PSSession -ConnectionURI "$connectionUri" -ConfigurationName Microsoft.Exchange -SessionOption $so'
if (-not $UseWIA)
{
$contents = $contents + ' -Authentication Kerberos -Credential $credential'
}
if ($SuppressError)
{
$contents = $contents + ' -erroraction silentlycontinue'
}
if ($AllowRedirection)
{
$contents = $contents + ' -AllowRedirection'
}
write-host -fore Yellow ("contents: " + $contents)
write-host -fore Yellow ("join n contents: " + [string]::join("`n", $contents))
[ScriptBlock] $command = $executioncontext.InvokeCommand.NewScriptBlock([string]::join("`n", $contents))
$session=invoke-command -Scriptblock $command
if (!$?)
{
# ERROR_ACCESS_DENIED = 5
# ERROR_LOGON_FAILURE = 1326
if (!(5 -eq $error[0].exception.errorcode) -and
!(1326 -eq $error[0].exception.errorcode))
{
#Write-Verbose ($ConnectFunctions_LocalizedStrings.res_0006 -f $fqdn)
return
}
else
{
# no retries if we get 5 (access denied) or 1326 (logon failure)
#$REVIEW$ connectedFqdn is not set. Is it okay?
break connectScope
}
}
$session
}