Search code examples
powershellpowershell-5.0winrmcimcommon-information-model

New-CimSession Auto Downgrade to DCOM / WMI


One of the benefits of CIM is the "fan out" ability; e.g. running the following causes sessions to be created against all servers in parallel, rather than sequentially:

$myServers = 1..10 | %{'MyServer{0:00}.mydomain.example.com' -f $_}
$cimSessions = New-CimSession -ComputerName $myServers

Since some of our servers don't have WimRM enabled, I want to gracefully downgrade to using DCOM when WimRM isn't available / so we don't fail if we don't have to. As such I put a wrapper around the Get-CimSession that does something like this (simplified version):

function New-SoEgCimSession { #(soeg = Stack Overflow Exempli Gratia)
    [CmdletBinding()]
    Param (
        [Parameter(Mantatory = $true, ValueFromPipeline = $true)]
        [String]$ComputerName
    )
    Begin {
        [Microsoft.Management.Infrastructure.Options.WSManSessionOptions]$WsmanSessionOption = New-CimSessionOption -Protocol Wsman
        [Microsoft.Management.Infrastructure.Options.DComSessionOptions]$DcomSessionOption = New-CimSessionOption -Protocol Dcom
    }
    Process {
        $session = New-CimSession -ComputerName $ComputerName -SessionOption $WsmanSessionOption
        if ($null -eq $session) {
            $session = New-CimSession -ComputerName $ComputerName -SessionOption $DcomSessionOption
        }
        $session
    }
}

... Which I'd now invoke like so:

$cimSessions = $myServers | New-SoEgCimSession

My worry is that this now removes the "fan out" ability, since each call to New-CimSession is separate / only takes 1 server argument.

Question

Is this worry valid, or does New-CimSession work more like async/await in C#, in that whilst waiting on one CimSession to be created, control's handed back to the parent thread which can fetch the next session?

Additional Thoughts / follow up question

If my worry is valid, is there a cleaner way of approaching this? I've considered using workflows to wrap this logic, but that feels like reinventing something that New-CimSession provides by default.

I've also considered calling New-CimSession with the array of computers and -ErrorAction Stop, then use a catch block to process any failures; but it seems that only the first failure's details are included (i.e. catch {$_.Exception.OriginInfo} returns a single server name, even if multiple servers fail). I've also not tested with a large enough group of servers to know if the error also terminates the attempt to create new sessions; with a small data set it doesn't, but that may be because all sessions are successfully created before the errors with the non-winrm servers are detected.

My current idea is to compare original array's computer names with those in the created sessions to find those for which sessions weren't created, then attempt to create DCOM sessions for those remaining. However that still feels like a slightly hacky workaround / I suspect that my lack of experience with CIM means I'm missing a more obvious out-of-the-box solution...


Solution

  • You can try the following:

    function New-SoEgCimSession {
      [CmdletBinding()]
      Param (
          [Parameter(Mantatory, ValueFromPipeline)]
          [String[]] $ComputerName
      )
    
      Begin {
          $names = [Collections.Generic.List[string]]::new()
          $WsmanSessionOption = New-CimSessionOption -Protocol Wsman
          $DcomSessionOption = New-CimSessionOption -Protocol Dcom
      }
    
      Process {
          $names.AddRange($ComputerName)
      }
    
      End {
          # Try WSMan first. Successful sessions will be output,
          # errors silently collected in variable $errs.
          New-CimSession -ErrorVariable errs -ErrorAction SilentlyContinue -ComputerName $names -SessionOption $WsmanSessionOption
          if ($errs.Count) {
            # Errors occurred; try all failing computers with DCOM now.
            # Successful sessions will be output and if there are still errors,
            # they will surface.
            $failedNames = $errs.OriginInfo.PSComputerName
            New-CimSession -ComputerName $failedNames -SessionOption $DcomSessionOption
          }
      }
    }
    
    • The computer names are collected in a list, and only after all have been collected does processing start, in the end block.

    • All names are passed to New-CimSession with the WSMan configuration first, suppressing display of any non-terminating errors (-ErrorAction SilentlyContinue), but collecting them in variable $errs (-ErrorVariable errs).

    • If any errors occurred, the computer names that caused the failure are then passed to New-CimSession with the DCOM configuration - no error handling is then attempted, meaning that any errors that still occur surface as non-terminating ones.

    • Finally, all successfully created sessions are output.


    Generally, using -ErrorAction Stop always aborts a command on the first non-terminating error; if you want to collect them all for later analysis, use something like -ErrorAction SilentlyContinue -ErrorVariable errs, where self-chosen variable $errs receives a collection of all non-terminating errors that occurred.