Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass an xml element to a scriptBlock when using invoke-command

Using PowerShell 3.0, I am using the Invoke-Command cmdlet to pass an xmlElement to a scriptblock. The problem is that I think the scriptBlock is receiving the parameter as an arrayList and not as an xmlElement (actually, I know that the received param is an ArrayList as I logged out $service.GetType() ).

Here is my XMLElement:

<Service>
    <ServiceName>Spooler</ServiceName>
    <Startup>Disabled</Startup>
</Service>

Here is my scriptBlock:

$scptModifyService = {
    param (
        $service
    );
    Set-Service -Name $service.ServiceName -StartupType $service.Startup;
}

Here is my code calling the scriptBlock:

## Modify remote Services:
foreach($service in $XML.QCSettings.Services.Service) {
    Invoke-Command -ScriptBlock $scptModifyService -ComputerName $ComputerName -ArgumentList $service;
}

From what I understand about the Invoke-Command cmdlet (get-help invoke-command) is that the ArguementList can take an object as input, so I don't think that is is an issue with how I call the scriptblock in the 'foreach' loop above. Hopefully that makes some sense.

Does anyone have any ideas about how capture the param in my scriptblock as an xmlElement and not as an ArrayList?

I have tried several things already (in my scriptblock):

param( $service = [xml]Get-Content args[0] );

And other variations on the that code.

like image 370
Nathan Bills Avatar asked Nov 02 '22 18:11

Nathan Bills


2 Answers

From what you've shown I don't see how the scriptblock is getting an ArrayList but you can still make this work. Does the ArrayList contain the correct XmlElements? If so, you can make your function more robust by just handling one or more service entries like so:

$scptModifyService = {
    param (
        [Xml.XmlElement[]]$service
    );

    foreach ($s in $service) {
        Set-Service -Name $s.ServiceName -StartupType $s.Startup;
    }
}

Then you should be able to call it like so:

Invoke-Command -ScriptBlock $scptModifyService -ComputerName $ComputerName -ArgumentList $XML.QCSettings.Services.Service
like image 76
Keith Hill Avatar answered Nov 08 '22 08:11

Keith Hill


You have to be careful when using PowerShell's [xml] accelerator and dynamic properties. What looks like a normal XML document object model gets casted around to ArrayList and other unsavory objects.

Sure enough, when I load your XML into a DOM, and call GetType() on the Service element, I get an Object[]:

> $xml = [xml]'<QCSettings><Services><Service /><Service /></Services></QCSettings>'
> $xml.QCSettings.Services.Service.GetType()
IsPublic IsSerial Name                                     BaseType            
-------- -------- ----                                     --------            
True     True     Object[]                                 System.Array        

Since you're using PowerShell 3, you can use the using scope to reference the XML block inside your remote script block:

$services = $xml.QCSettings.Services.Service
Invoke-Command -ScriptBlock { 
    $using:services | ForEach-Object {
        Set-Service $_.ServiceName -StartupType $_.StartupType
    }
}

Since this code passes in all the service objects, you're only logging into each computer once, whereas before, you were logging in once per service.

If you can't use the using scope, I would just live with the ArrayList and write your script block to accept a list of objects:

$scptModifyService = {
    param (
        [Object[]]
        $service
    );
    $service | ForEach-Object {
        Set-Service -Name $_.ServiceName -StartupType $_.StartupType
    }
}

$XML.QCSettings.Services.Service |
    ForEach-Object {
        Invoke-Command -ScriptBlock $scptModifyService -ComputerName $ComputerName -ArgumentList $_
    }
like image 31
Aaron Jensen Avatar answered Nov 08 '22 08:11

Aaron Jensen