0

Full disclosure: My problem may be based on a incomplete understanding of the Citrix PowerShell module for Xen Desktop.

I have the following script block. It's called in a loop, once for each VM in a list. I'm using PowerShell Jobs because I want to keep the UI thread free to update the UI while the jobs run.

Code "A"

$j = Start-Job -Name $jobName -ScriptBlock {
    param($url, $uuid, $cred, $snapshotName)
    $ErrorActionPreference = "Stop" 
    try
    {
        $error.clear()
        $xSS = $cred | Connect-XenServer -url $url -NoWarnCertificates -SetDefaultSession -PassThru; 
        $vm = (Get-XenVM -SessionOpaqueRef $xss.opaque_ref -uuid $uuid)
        #Create snapshot
        Invoke-XenVM -Async -SessionOpaqueRef $xss.opaque_ref -VM $vm -XenAction Snapshot -NewName $snapshotName
        return "OK"
    }
    catch
    {
        return ("ERROR: "+$error)
    }
} -ArgumentList $global:configFileVmMetaData.poolUrl, $xenVm.key, $global:cred, $snapshotName

Code "A" works ok, but it takes longer then necessary because I'm executing the Connect-XenServer cmdlet each time I invoke the script block.

So, I tried calling Connect-XenServer once outside the loop and passing in the session variable as shown below in Code "B". The result was the error Could not find open sessions to any XenServers being thrown inside the script block. I'm assuming that the $xss session variable is getting whanged somehow when its passed into the script block.

Any ideas why $xss session variable is getting whanged?

Code "B"

$xSS = $cred | Connect-XenServer -url $global:configFileVmMetaData.poolUrl -NoWarnCertificates -SetDefaultSession -PassThru; 

loop
{

    $j = Start-Job -Name $jobName -ScriptBlock {
        param($xss, $uuid, $snapshotName)
        $ErrorActionPreference = "Stop" 
        try
        {
            $error.clear()
            $vm = (Get-XenVM -SessionOpaqueRef $xss.opaque_ref -uuid $uuid)
            #Create snapshot
            Invoke-XenVM -Async -SessionOpaqueRef $xss.opaque_ref -VM $vm -XenAction Snapshot -NewName $snapshotName
            return "OK"
        }
        catch
        {
            return ("ERROR: "+$error)
        }
    } -ArgumentList $xss, $xenVm.key, $snapshotName

}

Additional examples inspired by the Robert Cotterman answer

In all cases, I still get the Could not find open sessions to any XenServers error

FYI - Using PowerShell 5.1

Example using $using. Variable contents are passed in and back out as expected

cls

$aLocal = "AAA"
$bLocal = "BBB"

$j = Start-Job -Name "TestJob" -ScriptBlock {
    return ($using:aLocal + " *** " + $using:bLocal)
    }

while ($true)
{
    $g = get-job -name "TestJob"

    write-host ("get-job " + $g.Name + " is " + $g.State)

    if ($g.State -ne "Running")
    {
        break
    }

    start-sleep -Seconds 1
}

write-host ("receive-Job='" + (receive-Job -Name "TestJob") +"'")

$g = get-Job -Name "TestJob"
Write-Host ("get-Job "+$g.name + " " + $g.state + " " + $g.HasMoreData + " " + $g.id)

if($g)
{
    Remove-Job -Name "TestJob"
}

Output

get-job TestJob is Running
get-job TestJob is Completed
receive-Job='AAA *** BBB'
get-Job TestJob Completed False 45
Remove-Job

Example using args. Variable contents are passed in and back out as expected

cls

$aLocal = "AAA"
$bLocal = "BBB"

$j = Start-Job -Name "TestJob" -ScriptBlock {
    return ($args[0] + " *** " + $args[1])
    } -ArgumentList ($aLocal, $bLocal)

while ($true)
{
    $g = get-job -name "TestJob"

    write-host ("get-job " + $g.Name + " is " + $g.State)

    if ($g.State -ne "Running")
    {
        break
    }

    start-sleep -Seconds 1
}

write-host ("receive-Job='" + (receive-Job -Name "TestJob") +"'")

$g = get-Job -Name "TestJob"
Write-Host ("get-Job "+$g.name + " " + $g.state + " " + $g.HasMoreData + " " + $g.id)

if($g)
{
    Remove-Job -Name "TestJob"
}

Output

get-job TestJob is Running
get-job TestJob is Completed
receive-Job='AAA *** BBB'
get-Job TestJob Completed False 49

Example using named args. Variable contents are passed in and back out as expected

cls

$aLocal = "AAA"
$bLocal = "BBB"

$j = Start-Job -Name "TestJob" -ScriptBlock {
    param($a, $b)
    return ($a + " *** " + $b)
    } -ArgumentList ($aLocal, $bLocal)

while ($true)
{
    $g = get-job -name "TestJob"

    write-host ("get-job " + $g.Name + " is " + $g.State)

    if ($g.State -ne "Running")
    {
        break
    }

    start-sleep -Seconds 1
}

write-host ("receive-Job='" + (receive-Job -Name "TestJob") +"'")

$g = get-Job -Name "TestJob"
Write-Host ("get-Job "+$g.name + " " + $g.state + " " + $g.HasMoreData + " " + $g.id)

if($g)
{
    Remove-Job -Name "TestJob"
}

Output

get-job TestJob is Running
get-job TestJob is Completed
receive-Job='AAA *** BBB'
get-Job TestJob Completed False 55
VA systems engineer
  • 2,856
  • 2
  • 14
  • 38
  • 1
    I just don't think it works that way. The connection to the Xen server is created for your PowerShell session, with info about that connection collected in the `$xss` variable. Each job runs its own PowerShell session. So just passing `$xss` to the job isn't the same, it's just a description of the connection details. To phrase that another way, I can write down all the details about my car and hand them to you, but you can't drive that slip of paper to work. – TheMadTechnician Oct 05 '18 at 19:08
  • Agreed. I need to rethink this. Please post as answer – VA systems engineer Oct 07 '18 at 14:05
  • powershell has a built in job wait. `get-job | wait-job` – Robert Cotterman Oct 09 '18 at 20:36

2 Answers2

1

Jobs and invoke-command require you to specify that you're using your variable. Simply change the variable from

$variable

To

$using:variable

Internal variables do not need this. But calling upon variables from the parent script do.

Alternatively, Since you're passing $xss as an argument, you wouldn't call it with $xss but rather

$args[0]

Since it's your first argument. And $args[1] for the second etc... The reason is because the entire xss variable is being printed as an argument and is not named inside the job. It's given the name $args and has a place in the first slot (0).

I prefer $using:variable as it reduces confusion

Robert Cotterman
  • 2,213
  • 2
  • 10
  • 19
  • See my additional coding examples (3 examples: $using, args, named parameters) above under "Additional examples inspired by `Robert Cotterman`". In all cases, I still get the `Could not find open sessions to any XenServers` error. I think TheMadTechnician is right in this case. – VA systems engineer Oct 09 '18 at 14:31
  • Well, i don't know how XenServers works, sorry about that. My suggestions worked perfectly. But not for your specific need. If i had it, i'd try to figure it out. Maybe if you have each job actually grab the xss info individually? So when it get's it, it's unique to it's own powershell instance? – Robert Cotterman Oct 09 '18 at 20:43
  • 1
    No worries - I appreciate you're taking the time. You made me explore the various ways of passing data into a script block. Also, I documented my problem and solution from a different perspective with [this S.O. question](https://stackoverflow.com/questions/52689603/how-can-i-keep-the-ui-responsive-and-updated-with-progress-while-all-the-work-is/52689639#52689639) – VA systems engineer Oct 10 '18 at 11:08
  • Did you try having each job perform it's own XSS cred grab? That sounds viable to me. – Robert Cotterman Oct 10 '18 at 16:40
  • Yes, but it takes 5-10 seconds to get XSS session, so it slows the overall work down if I have say 20-30 snapshots to create. I ended up moving the whole loop into the job so I just had to get XSS session once per job. See the S.O. question I referenced in my comment to you – VA systems engineer Oct 11 '18 at 11:09
1

I just don't think it works that way. The connection to the Xen server is created for your PowerShell session, with info about that connection collected in the $xss variable. Each job runs its own PowerShell session. So just passing $xss to the job isn't the same, it's just a description of the connection details.

TheMadTechnician
  • 34,906
  • 3
  • 42
  • 56