none
Import-PSSession w/Remote Exchange within Runspaces RRS feed

  • Question

  • Thanks in advance for your help. I am having difficulty using the Import-PsSession to import a remote Exchange shell session within a runspace. My underlying goal is I want to multi-thread some Exchange-related operations with EWS Managed API. I'm trying to spread these connections among six Exchange servers in our environment. However when I do so results are inconsistent; it's random how many of the six servers will actually return results and which will error out. The error I receive:

    PS>TerminatingError(Import-PSSession): "Data returned by the remote Get-FormatData command is not in the expected format."
    import-pssession : Data returned by the remote Get-FormatData command is not in the expected format.
    At line:12 char:13
    +             import-pssession $session -AllowClobber
    +             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidResult: (:) [Import-PSSession], ArgumentException
        + FullyQualifiedErrorId : ErrorMalformedDataFromRemoteCommand,Microsoft.PowerShell.Commands.ImportPSSessionCommand

    I've created the below script (with copious help from other TechNet articles) to produce these errors in hope of figuring out how to fix them. FYI we run Exchange 2016 CU12 on-prem.

        # I didn't write this function either but for the life of me I can't find where it came from to credit the author.
        function Resize-Array ([object[]]$InputObject, [int]$SplitSize)
        {
    	    $length = $InputObject.Length
    	    for ($Index = 0; $Index -lt $length; $Index += $SplitSize)
    	    {
    		    , ($InputObject[$index .. ($index + $splitSize - 1)])
    	    }
        }
    
    $InputObject = (get-aduser -Filter {extensionattribute2 -eq "a"} -ResultSetSize 100).samaccountname
    $bucketsize = 10
    
        # Segregate our list of targets into a set of arrays
        $TargetsArray = resize-array -inputobject $InputObject -SplitSize $bucketsize
    
        # Create the pool of runspaces
        [void][runspacefactory]::CreateRunspacePool()
        # Define initial session state
        $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
        # Define the pool and the max number of runspaces to be active
        $RunspacePool = [runspacefactory]::CreateRunspacePool(1,3)
        # Create a new PS session
        $PowerShell = [powershell]::Create()
        # Assign our runspace pool to this new session and open it.
        $PowerShell.RunspacePool = $RunspacePool
        [void]$RunspacePool.open()
    
        $jobs = new-object system.collections.arraylist
        $servers = get-exchangeserver | where {$_.name -like "dc1*"}
        $counter = 0
        $serverCounter = 0
    
        foreach ($array in $TargetsArray)
            {
            $PS = [powershell]::Create()
            $PS.RunspacePool = $RunspacePool
            [void]$PS.AddScript({
                Param ($target,$server)
                
                start-transcript ($env:USERPROFILE + "\desktop\test\" + $server + ".txt")
    
                $URI = "http://" + $server + "/PowerShell/"
                $session = new-pssession -ConfigurationName Microsoft.Exchange -ConnectionUri $URI -Authentication Kerberos
                import-pssession $session -AllowClobber
    
                foreach ($thing in $target)
                    {
                    [pscustomobject]@{
                    Target = $thing
                    Server = $server
                    Result = (get-mailbox $thing).distinguishedname
                    Errors = $Error
                        }
                    }
                Stop-Transcript
                })
            $PS.AddParameter("Target",$Array).AddParameter("Server",$servers[$serverCounter])
            # Begin execution of this runspace
            $Handle = $PS.BeginInvoke()
            $temp = "" | select PowerShell,Handle
            $temp.PowerShell = $PS
            $temp.Handle = $handle
            [void]$jobs.add($temp)
    
            $ServerCounter++
            # If our ServerCounter is equal to the count of available servers to connect to - we use equal because the count of servers is going to be one more than the index of the array $servers.
            # In other words $servers[4] would be the fifth server in the list because we index upwards from 0.
            if ($ServerCounter -ge ($servers | measure).count)
                {
                # If we've reached the end of the list of servers then start again from the first one.
                $ServerCounter = 0
                }
            }
    
            #Verify completed
        do
            {
            Write-host -ForegroundColor DarkGreen (“Available Runspaces in RunspacePool: {0}” -f $RunspacePool.GetAvailableRunspaces())
            Write-host -ForegroundColor DarkGreen (“Remaining Jobs: {0}” -f @($jobs | Where {$_.handle.iscompleted -ne ‘Completed’}).Count)
            start-sleep 5}
        until (($jobs | Where {$_.handle.iscompleted -ne ‘Completed’}).Count -eq 0)
    
        $return = $jobs | ForEach {
            $_.powershell.EndInvoke($_.handle)
            $_.PowerShell.Dispose()
        }
    
        $jobs.clear()


    • Edited by JdeFalconr Thursday, August 22, 2019 3:57 PM
    Thursday, August 22, 2019 3:55 PM

All replies

  • Most of your code is wrong or unused.

    This is all you need to do to create a PowerShell session.

    $powerShell = [powershell]::Create()
    $powerShell.Runspace = [runspacefactory]::CreateRunspace()
    $powerShell.Runspace.Open()
    

    Delete the rest of the code that creates the PS session.

    You also need to add error handling to prevent false errors.

    $script = {
        Param (
            $target,
            $server
        )
        
        start-transcript ($env:USERPROFILE + "\desktop\test\" + $server + ".txt")
        
        Try{
            $URI = "http://" + $server + "/PowerShell/"
            $session = new-pssession -ConfigurationName Microsoft.Exchange -ConnectionUri $URI -Authentication Kerberos
            import-pssession $session -AllowClobber
            
            foreach ($thing in $target) {
                [pscustomobject]@{
                    Target = $thing
                    Server = $server
                    Result = (get-mailbox $thing).distinguishedname
                    Errors = $Error
                }
            }
        }
        Catch{
            Throw $_
        }
        Stop-Transcript
    }
    
    foreach($array in $TargetsArray){
        
        $powerShell = [powershell]::Create()
        $powerShell.Runspace = [runspacefactory]::CreateRunspace()
        $powerShell.Runspace.Open()
        
        [void]$powerShell.AddScript($script)
        $powerShell.AddParameter('Target', $Array)
        $powerShell.AddParameter("Server", $servers[$serverCounter])
        
        $asyncResult = $powerShell.BeginInvoke()
        
        # ...
    }
    
    You should just use a workflow which will manage all of your runspaces dynamically with one simple line of code.


    \_(ツ)_/

    Thursday, August 22, 2019 5:12 PM
    Moderator
  • Most of your code is wrong or unused.

    This is all you need to do to create a PowerShell session.

    $powerShell = [powershell]::Create()
    $powerShell.Runspace = [runspacefactory]::CreateRunspace()
    $powerShell.Runspace.Open()

    Delete the rest of the code that creates the PS session.

    You also need to add error handling to prevent false errors.

    $script = {
        Param (
            $target,
            $server
        )
        
        start-transcript ($env:USERPROFILE + "\desktop\test\" + $server + ".txt")
        
        Try{
            $URI = "http://" + $server + "/PowerShell/"
            $session = new-pssession -ConfigurationName Microsoft.Exchange -ConnectionUri $URI -Authentication Kerberos
            import-pssession $session -AllowClobber
            
            foreach ($thing in $target) {
                [pscustomobject]@{
                    Target = $thing
                    Server = $server
                    Result = (get-mailbox $thing).distinguishedname
                    Errors = $Error
                }
            }
        }
        Catch{
            Throw $_
        }
        Stop-Transcript
    }
    
    foreach($array in $TargetsArray){
        
        $powerShell = [powershell]::Create()
        $powerShell.Runspace = [runspacefactory]::CreateRunspace()
        $powerShell.Runspace.Open()
        
        [void]$powerShell.AddScript($script)
        $powerShell.AddParameter('Target', $Array)
        $powerShell.AddParameter("Server", $servers[$serverCounter])
        
        $asyncResult = $powerShell.BeginInvoke()
        
        # ...
    }
    You should just use a workflow which will manage all of your runspaces dynamically with one simple line of code.


    \_(ツ)_/

    Thanks for your reply, can you provide more information about how this resolves the issue I presented? I appreciate the time you took to critique my example script but I don't know that it addresses the issue at hand. While I don't disagree there may be more efficient ways to achieve this result I've used that same runspace code successfully before (albeit without using the Import-PsSession cmdlet which is the central issue to this post) without issues. It's largely taken from https://devblogs.microsoft.com/scripting/beginning-use-of-powershell-runspaces-part-1/. Unless you can demonstrate this runspace code is the cause of my problem then critiquing that portion of my example script isn't relevant.

    Thursday, August 22, 2019 5:58 PM
  • Without correct coding and error handling I see no way to determine your issue.

    Start smale and just run on copy as I posted it to debug where your issue is.


    \_(ツ)_/

    Thursday, August 22, 2019 7:42 PM
    Moderator
  • Just run the following and check the errors.

    $script = {
        Param (
            $target,
            $server
        )
        
        start-transcript (Join-Path $env:USERPROFILE "\desktop\test\$server.txt")
        
        $ErrorActionPreference = 'Stop'
        Try{
            $URI = "http://$server/PowerShell/"
            $session = new-pssession -ConfigurationName Microsoft.Exchange -ConnectionUri $URI -Authentication Kerberos
            import-pssession $session -AllowClobber
            
            foreach ($thing in $target) {
                [pscustomobject]@{
                    Target = $thing
                    Server = $server
                    Result = (get-mailbox $thing).distinguishedname
                }
            }
        }
        Catch{
            Throw $_
        }
        Stop-Transcript
    }
    $powerShell = [powershell]::Create()
    $powerShell.Runspace = [runspacefactory]::CreateRunspace()
    $powerShell.Runspace.Open()
    
    [void]$powerShell.AddScript($script)
    $powerShell.AddParameter('Target', $Array)
    $powerShell.AddParameter('Server', 'someserver')
    
    $asyncResult = $powerShell.BeginInvoke()
    
    

    Once you can get the core code working you can then add the remaining bits as needed.


    \_(ツ)_/

    Thursday, August 22, 2019 7:50 PM
    Moderator
  • I should also not that the very first thing you want to do is just test the script you are tryi g to run I a runspace to be sure you have no errors.

    Just run this:

    $script = {
        Param (
            $target,
            $server
        )
        
        start-transcript (Join-Path $env:USERPROFILE "\desktop\test\$server.txt")
        
        $ErrorActionPreference = 'Stop'
        Try{
            $URI = "http://$server/PowerShell/"
            $session = new-pssession -ConfigurationName Microsoft.Exchange -ConnectionUri $URI -Authentication Kerberos
            import-pssession $session -AllowClobber
            
            foreach ($thing in $target) {
                [pscustomobject]@{
                    Target = $thing
                    Server = $server
                    Result = (get-mailbox $thing).distinguishedname
                    Errors = $Error
                }
            }
        }
        Catch{
            Throw $_
        }
        Stop-Transcript
    }
    
    $script.Invoke($Array, 'someserver')

    When the script is certain to run succesfully then you can try it in the runspace. 

    You may have to set "STA" mode for the script to work.


    \_(ツ)_/

    Thursday, August 22, 2019 8:16 PM
    Moderator
  • Hi,

    Was your issue resolved?

    If you resolved it using our solution, please "mark it as answer" to help other community members find the helpful reply quickly.

    If you resolve it using your own solution, please share your experience and solution here. It will be very beneficial for other community members who have similar questions.

    If no, please reply and tell us the current situation in order to provide further help.

    Best Regards,

    Lee


    Just do it.

    Friday, September 6, 2019 11:58 AM
    Moderator