1

It seems to be strange but I can't assign a value to variable inside of Invoke-Command. Here is the code below but when print out $targetComputerPath it's simply empty. What's wrong?

foreach ($item in $computersPath){

    $computername = $item.Name
    $username = $item.UserID

    Write-Host computer $computername and user $username

    if (Test-Connection -ComputerName $computername -Count 1 -ErrorAction SilentlyContinue)
    {
        if ($((Get-Service WinRM -ComputerName $computername).Status) -eq "stopped")
        {
          (Get-Service WinRM -ComputerName $computername).Start()
        } 
        Invoke-Command -ComputerName $computername -ScriptBlock {

        if ($((Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").ReleaseId) -eq "1903" ) 
            {
               $targetComputerPath = "\\"+$computername+"\c$\Users\"+$username+"\Desktop\"
               write-host "1903"
            } 
        else 
            {
              $targetComputerPath = "\\"+$computername+"\c$\Users\"+$username+"\Desktop\"
              write-host "something else"
            } 
        }
    }
    write-host $targetComputerPath
}
Dmitry Dorofeev
  • 85
  • 1
  • 1
  • 13
  • @AdminOfThings: In general, yes, but here _remoting_ is involved, so you fundamentally cannot modify the caller's variables from a _remotely executing_ script block. – mklement0 Jan 27 '20 at 18:21
  • Side note: To figure out the Windows version of a device, a look at the AD record is enough (`Get-ADComputer $computername -Properties operatingSystemVersion | select name,operatingSystemVersion` - this works even when the machine is offline). If this is all you need to know, you can cut out WinRM/`Invoke-Command` entirely and simplify your script a great deal. – Tomalak Jan 27 '20 at 18:27

1 Answers1

2

The point of WinRM is that you take a script block, and execute it on a different machine.

None of the variables you define in the host script will be available on the remote machine.

This becomes more apparent when you separate the "task", a.k.a the script block, from the Invoke-Command, like this:

$task = {
    $version = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
    if ($version.ReleaseId -eq "1903") {
        # note that `$username` cannot be available here, it's never been defined!
        return "\\$env:COMPUTERNAME\c$\Users\$username\Desktop"
    } else {
        return "\\$env:COMPUTERNAME\c$\Users\$username\Desktop"
    } 
}

foreach ($item in $computersPath) {
    $computername = $item.Name
    $username = $item.UserID

    Write-Host computer $computername and user $username

    if (Test-Connection -ComputerName $computername -Count 1 -ErrorAction SilentlyContinue) {
        $winrm = Get-Service WinRM -ComputerName $computername
        if ($winrm.Status -eq "stopped") { $winrm.Start() }
        $targetComputerPath = Invoke-Command -ComputerName $computername -ScriptBlock $task
        Write-Host "The machine returned: $targetComputerPath"
    }
}

As you can see, you can return values from the script block and they will be available as the return value of Invoke-Command.

If you want to pass arguments to your script block, this thread talks about that: How do I pass named parameters with Invoke-Command?

Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • 1
    Well done; the linked question is more specifically about passing _named_ arguments to `Invoke-Command`; I suggest (also) linking to https://stackoverflow.com/q/35492437/45375 – mklement0 Jan 27 '20 at 18:20
  • 1
    Between those two links, the OP should be able to figure it out. – Tomalak Jan 27 '20 at 18:23