Invoke-Command with variables

This topic contains 6 replies, has 2 voices, and was last updated by Profile photo of Dean Cefola Dean Cefola 3 months, 2 weeks ago.

Viewing 7 posts - 1 through 7 (of 7 total)
  • Author
    Posts
  • #41954
    Profile photo of Dean Cefola
    Dean Cefola
    Participant

    I want to pass variables to invoke-command running commands remotely. I am missing something...here is what I have

    Trying to pass a variable for the Event Log Name "System" but the error tells me that the variable $Log is null

    Cannot bind argument to parameter 'LogName' because it is null.
    + CategoryInfo : InvalidData: (:) [Get-EventLog], ParameterBindingValidation
    Exception
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.Po
    werShell.Commands.GetEventLogCommand
    + PSComputerName : DC

    $Log = 'System'
    $Session = New-PSSession -ComputerName DC -Credential $cred
    $Command = {Get-EventLog -LogName $Log -Newest 3}

    Invoke-Command -Session $Session -ScriptBlock $Command -ArgumentList $Log

    Remove-PSSession -Session $Session

    Appreciate the help....

    #41964
    Profile photo of Jack Neff
    Jack Neff
    Participant

    The scriptblock has it's own variable scope like a function or another script so you have to add a param block so it knows to expect a variable. The variable names inside and outside can be the same. I make them different for my own sanity.

    $Log = 'System'
    $Session = New-PSSession -ComputerName DC -Credential $cred
    $Command = {param($LogName);  Get-EventLog -LogName $LogName -Newest 3}
    
    Invoke-Command -Session $Session -ScriptBlock $Command -ArgumentList $Log
    
    Remove-PSSession -Session $Session
    
    #41980
    Profile photo of Dean Cefola
    Dean Cefola
    Participant

    Thank you that does work....

    I am wondering if I missed this in the PowerShell Help or if there is a gap here that needs filling... 🙂

    I did not see anything in help, or any articles online saying that scriptblocks have param blocks and that you need that so you can use the argumentlist to pass variables to invoke-command on remote machines.

    Also you said that you keep the names different for your sanity. I am not sure I am following your thinking here.
    If I keep the $LOG variable the same in the $LOG = 'System' as well as in the Param block and in the command....and in the argument list doesn't that make it simpler to follow which variables you are using or having issues with?

    what is the reason you want them to be different?

    Thanks again for your help

    #41987
    Profile photo of Jack Neff
    Jack Neff
    Participant

    Yea the help file isn't real clear on it but it must be in an article somewhere I know I didn't come up with it, I'm not that smart! When executing code on remote machines you always have to keep 'context' in mind since each remote machine will have it's own unique set of environmental values and properties.

    You don't have to use a param block either, I think most folks reference variables positionally inside the scriptblock using the $Args automatic variable. Same concept as %1 in batch scripts or WScript.Arguments in vbscript.

    Example 1:

    $One = "I"
    $Two = "Like"
    $Three = "Pizza"
    
    Invoke-Command -ComputerName $Host -ScriptBlock { Write-Output "$($args[0]) $($args[1]) $($args[2])" } -ArgumentList $One,$Two,$Three
    

    Example 2:

    $Arguments = "I","Like","Pizza"
    
    Invoke-Command -ComputerName $Host -ScriptBlock { Write-Output "$($args[0]) $($args[1]) $($args[2])" } -ArgumentList $Arguments
    

    Why do I change the variable names? Just a general rule of thumb for myself anytime I'm writing a script that passes variables between scopes I should keep things clear. Mixing up values could spell trouble...

    Example 3:

    $AppropriateResponse = "Yes dear"
    
    function Set-NewResponse {
        $NewResponse = "No dear"
        Return $NewResponse
    }
    
    switch ($WifeAsks){
    
        "Do you still think I'm pretty?" {
            Write-Output $AppropriateResponse
        }
    
        "Does this dress make me look fat?" {
            $AppropriateResponse = Set-NewResponse
            Write-Output $AppropriateResponse
        }
        "Lets have another baby" {
            Exit
        }
    
    }
    
    #42147
    Profile photo of Dean Cefola
    Dean Cefola
    Participant

    OK so I have taken your example to the next level...not sure if that is up or sideways 🙂

    trying to use an import command from SCCM on a remote computer with another users Creds.

    Here is the original command which works fine.

    Import-CMComputerInformation `
    -CollectionName $TargetCollection `
    -FileName $ImportFile `
    -VariableName 'Domain'

    The $($Args[x]) variables work fine from your example...however they do not work in the script execution

    I have listed the $($Args[x]) variables in the $Command so you can see that they work, but when I try to pass it to the commands to create a new PS Drive or perform the import they do not work.
    and yes I have tried to take them out and let it run....no dice.

    Set-Location -Path C:
    $FQDN = "$Server.domain.com"

    $Session = New-PSSession `
    -ComputerName $Server `
    -Credential $cred
    $Command = {
    $($Args[0])
    $($Args[1])
    $($Args[2])
    $($Args[3])
    $($Args[4])
    New-PSDrive `
    -Name $($Args[0]) `
    -PSProvider CMSite `
    -Root $($Args[1])

    Set-Location -Path $($Args[2])

    Import-CMComputerInformation `
    -CollectionName $($Args[3]) `
    -FileName $($Args[4]) `
    -VariableName 'Domain'

    Set-Location -Path C:
    }
    Invoke-Command `
    -Session $Session `
    -ScriptBlock $Command `
    -ArgumentList $CMSite, $FQDN, $SCCM_Site_Code, $TargetCollection, $ImportFile

    Remove-PSSession `
    -Session $Session

    Thanks again,

    #42156
    Profile photo of Jack Neff
    Jack Neff
    Participant

    Try using the pre blocks to post code. Let's post again for clarity...

    #You haven't declared $TargetCollection or $ImportFile yet.  Can't use them until you do
    Import-CMComputerInformation -CollectionName $TargetCollection -FileName $ImportFile -VariableName 'Domain'
    
    #Why this?
    Set-Location -Path C:
    
    #You haven't declared $Server yet
    $FQDN = "$Server.domain.com"
    
    #You haven't declared $Cred yet
    $Session = New-PSSession -ComputerName $Server -Credential $cred
    
    $Command = {
    
        #Can't connect to network resources in remote script due to second-hop CredSSP problem
        #Also PSProvider needs to be 'FileSystem'
        New-PSDrive -Name $($Args[0]) -PSProvider CMSite -Root $($Args[1])
    
        #Wouldn't it be easier to just use the full path in $ImportFile?
        Set-Location -Path $($Args[2])
    
        Import-CMComputerInformation -CollectionName $($Args[3]) -FileName $($Args[4]) -VariableName 'Domain'
    
        #Why set this when no other execution follows?
        Set-Location -Path C:
    }
    
    #You haven't declared $CMSite, $SCCM_Site_Code, $TargetCollection, or $ImportFile
    Invoke-Command -Session $Session -ScriptBlock $Command -ArgumentList $CMSite, $FQDN, $SCCM_Site_Code, $TargetCollection, $ImportFile
    
    Remove-PSSession -Session $Session
    

    I put some questions in as comments but I have a few more. At the heart of this, you're executing Import-CMComputerInformation on both the local machine and a remote server. Are you doing this to pre-stage machines for OSD imaging? Do your site servers belong to the same hierarchy? If they are, you should only need to import them into the top level CAS then all site servers will have access to the device information. If they are not in the same hierarchy, why import the same set of computers in two separate environments? So maybe it would help to understand what your trying to accomplish? But back to the code...

    One issue I noticed was inside the $Command you attempt to connect a PSDrive. This is the infamous 'Second-Hop' issue and might be a PS gotcha? Don wrote a good article about it here. Essentially it means you can use stored credentials to connect to network resources directly from your local machine to a remote computer (one hop) but you can't execute script on a remote machine that attempts to reach out to a third machine (second hop) without enabling CredSSP.

    #42713
    Profile photo of Dean Cefola
    Dean Cefola
    Participant

    This script was part of a larger function so I didn't include all of it since there was no issue with it. I was trying to change the code to function on the remote server with invoke-command.

    Here is the whole script. It was originally meant to be interactive, now I want it to start interactively but do the work directly on the remote server with specific credentials

    
    Function Import-Device {
    
    [CmdletBinding()]
    Param (
            [Parameter(Mandatory=$true)]
                [validateset('Model','Prod')]
                    [string]$SiteCode,
            [Parameter(Mandatory=$true)]
                [string]$ImportFile,        
             [Parameter(Mandatory=$true)]
                [ValidateSet ('Windows7','Windows10','Server2008-R2', 'Server2012-R2')]
                    [string]$TargetOS
        )
    
    Begin {
        Write-Host `
            -ForegroundColor Green `
            -BackgroundColor Black `
            "Setting up environment, this will only take a moment..."
            ""
            ""
        Import-Module ($Env:SMS_ADMIN_UI_PATH.Substring(0,$Env:SMS_ADMIN_UI_PATH.Length-5) + '\ConfigurationManager.psd1')
        
        $password = "xxxxencryptedPasswordxxxxxxxxxxxxxxxx"
        $key = "#######encryptedkey###########"
        $passwordSecure = ConvertTo-SecureString -String $password -Key ([Byte[]]$key.Split(" "))
        $cred = New-Object system.Management.Automation.PSCredential("DOMAIN\User_ID", $passwordSecure)
         
         $Array = @(
            @{Code = 'PAT' ; Name = 'Prod'  ; Server = 'Server1'}
            @{Code = 'MCJ' ; Name = 'Model' ; Server = 'Server2'}
        )    
        $Code = $Array | ? Name -EQ $SiteCode
        $CMServer = $Code.Server
        $CMSite = $Code.Code    
        $SCCM_Site_Code = $Code.Code + ':'
        
        cd $SCCM_Site_Code
        $Source = Get-Content -LiteralPath $ImportFile   
        
        $OSArray = @(
            @{OS = 'Windows7'   ; Collection = 'Windows 7 Deployment Collection'}
            @{OS = 'Windows10'  ; Collection = 'Windows 10 Deployment Collection'}
            @{OS = 'Server2008-R2' ; Collection = 'Server 2008 Deployment Collection'}
            @{OS = 'Server2012-R2' ; Collection = 'Server 2012 Deployment Collection'}
            @{OS = 'Server2016' ; Collection = 'server 2016 Deployment Collection'}
        )
            $OSCode = $OSArray | Where-Object -Property OS -EQ $TargetOS    
        $TargetCollection = $OSCode.Collection
        New-PSDrive `
            -Name $CMSite `
            -PSProvider CMSite `
            -Root "$CMServer.Domain.com"
        
    }
    
    Process {
        Write-Host `
            -ForegroundColor Green `
            -BackgroundColor Black `
            "You requested to import the following device(s)"
            ""
            ""
        foreach ($PC in $Source) { 
           $Names =  $PC.Split(',,')[0]           
            Write-Host `
                -BackgroundColor Black `
                -ForegroundColor Green `
                $Names           
            }  
    ""
    ""
        Write-Host `
            -BackgroundColor Black `
            -ForegroundColor Magenta `
            "Testing if the device(s) you want to import already exist" 
        "" 
        ""   
        foreach ($PC in $Source) { 
            $obj = New-Object -TypeName PSObject 
                $obj | add-member -MemberType NoteProperty -Name Name -Value   ($PC.Split(',,')[0]) 
                $obj | add-member -MemberType NoteProperty -Name MAC -Value    ($PC.Split(',,')[2]) 
                $obj | add-member -MemberType NoteProperty -Name Domain -Value ($PC.Split(',,')[4]) 
                    
            $devicename = $obj.name 
            $devicemac = $obj.MAC
            $devicevariable = $obj.Domain
              
            if ((get-CMDevice -Name $devicename) -ne $null) { 
                
                Write-Host `
                    -BackgroundColor Black `
                    -ForegroundColor Red `
                    "An Existing Device was found..." 
                ""
                $obj
                ""
                Write-Host `
                    -BackgroundColor Black `
                    -ForegroundColor Red `
                    "Please Remove the existing line from the Import.csv file"  
                    "" 
                    "" 
                Read-Host -Prompt "The Import Process will now Terminate"
                Set-Location -Path C:
                Exit 
            }
        }
    
        foreach ($PC in $Source) { 
            $obj = New-Object -TypeName PSObject 
                $obj | add-member -MemberType NoteProperty -Name Name -Value   ($PC.Split(',,')[0]) 
                $obj | add-member -MemberType NoteProperty -Name MAC -Value    ($PC.Split(',,')[2]) 
                $obj | add-member -MemberType NoteProperty -Name Domain -Value ($PC.Split(',,')[4]) 
                    
            $devicename = $obj.name 
            $devicemac = $obj.MAC
            $devicevariable = $obj.Domain
              
            Write-Output $Obj                    
    
            Import-CMComputerInformation `
                -CollectionName $TargetCollection `
                -FileName $ImportFile `
                -VariableName 'Domain'      
        } 
    
        ""
        Write-Host `
            -ForegroundColor Green `
            -BackgroundColor Black `
            "Import Complete, now configuring new devices...
    Process should complete in... 2 minute(s) or less"
            ""
            ""
    
        Wait-Event -Timeout 10
        
        Invoke-CMDeviceCollectionUpdate -Name 'All Systems'
        
        Wait-Event -Timeout 10
            
        Invoke-CMDeviceCollectionUpdate -Name $TargetCollection 
    }    
     
     End {   Write-Output "#———————————————-#
    #———————————————-#
    #———————————————-#
    #———————————————-#
    #———————————————-#
    #———————————————-#"
        ""
        Write-Host `
            -BackgroundColor Black `
            -ForegroundColor Green `
            "The Following Devices have successfully been Imported"
        ""
        ""  
          
        foreach ($PC in $Source) { 
            $Names =  $PC.Split(',,')[0]   
            (Get-CMDevice -Name $names).name
        } 
        ""
        ""
        Write-Host `
            -ForegroundColor Green `
            -BackgroundColor Black `
            "The Import Process is complete."
        ""
        Set-Location -Path c:
        Read-Host -Prompt "Press Enter to close the script window"
        Exit
    
    }
    }
    
    
    
    

    The segment that needs to change is in the Process block, in the second Foreach loop where the Import-CMComputerInformation command is.

    I want to put the invoke-command here so I can use different credentials and run the process on the SCCM Site server directly.

    thanks,

Viewing 7 posts - 1 through 7 (of 7 total)

You must be logged in to reply to this topic.