Unable to Invoke msiexec through script

Hi all, I have a script that does a couple of things, it’s primary function is to install software remotely and quietly

  1. Prompts user to input machine name
  2. Set-Location to network shared directory containing software
  3. From that directory, Copy-Item to remote admin share
  4. Invoke-Command msiexec.exe to quietly install software using local path to copied software
  5. Remove-Item the copied software

Running the script, the file get successfully copied to their location. However when trying to Invoke-Command, the software does not install. On top of that, when trying to remove the file, error feeds back:

Cannot bind argument to parameter 'Path' because it is null.
    + CategoryInfo          : InvalidData: (:) [Remove-Item], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.RemoveItemC
   ommand

I recognize that there’s something wrong with how I’ve stored the path in the variable, but I’ve had the script write the output of the install directory path and it looks fine. Also, if I do these step by step in the terminal, software successfully installs quietly. Here is my actual script:

$workingDirectory = $PSScriptRoot
$user = (whoami).Split('\')[1]
$remoteComputer = Read-Host "Please enter computer name"
$t2Profile = "\\computer\C$\Users\$user\installdirectory" # this uses admin share to copy files over

if (Test-Path $t2Profile){
    if(-not(Test-Path "$t2Profile\installfolder")){
        New-Item -ItemType Directory "$t2Profile\installfolder"
    } else {
        $atlas = "\\path\to\file\share\Mozilla Firefox\126.0" # file in \126.0 directory is called "Firefox Setup 126.0.msi"
        $msiName = (Get-ChildItem $atlas).Name
        
        Set-Location $atlas # set location to the network shared directory so that Copy-Item circumvents double-hop issues
        Write-Output "Copying $atlas\$msiName to $remoteComputer"
        Copy-Item -Path ".\*msi" -Destination "$t2Profile\installfolder" # Copy the .msi setup file to the \installfolder
        Set-Location $workingDirectory 
        
        $remoteDirectory = "\\$remoteComputer\C$\Users\$user\installdirectory\$msiName"
        $installDirectory = "C:\Users\$user\installdirectory\$msiName" # this is the variable I use for the path in invoke-command

        if(Test-Path "$remoteDirectory"){
            Invoke-Command -ComputerName $remoteComputer -ScriptBlock {msiexec.exe /i "$installDirectory" /qn} #invoke msiexec on the remote machine using path to copied .msi
            Invoke-Command -ComputerName $remoteComputer -ScriptBlock {Remove-Item "$installDirectory"}
        } else {
            Write-Host "Something went wrong"
        }
        

        
    } 
} else {
    Write-Host "Profile has not been created on this machine. Please use 'Enter-PSSession <computer name>' to add your profile, then rerun this script."
}

If anyone has recommendations or ideas please let me know. Thank you!

Anytime you pass variables from the script main body to Invoke-Command, you need to use the “Using” Scope.

-ScriptBlock {msiexec.exe /i "$Using:installDirectory" /qn}
2 Likes

Wow I feel silly, thank you that’s incredibly good to know. I thought it would just pass the variable to invoke as a string.

Apologies here for adding to the convo after resolved, but just wanted to say I love the name @xml :smiley:

1 Like

I would say “can use the using scope modifier” versus “need to use” as there is another option. You can pass in the data with the -ArgumentList parameter using either automatic variables or explicit parameters.

explicit variable declaration

Invoke-Command -ComputerName $remoteComputer -ScriptBlock {
    Param(
        $installDirectory
    )
    msiexec.exe /i "$installDirectory" /qn
} -ArgumentList $installDirectory

In this example we passed in the $installDirectory value to the parameter installDirectory. It doesn’t have to be the same name and sometimes it’s less confusing to keep them different

Invoke-Command -ComputerName $remoteComputer -ScriptBlock {
    Param(
        $LocalPath
    )
    msiexec.exe /i "$LocalPath" /qn
} -ArgumentList $installDirectory

Automatic variables

Invoke-Command -ComputerName $remoteComputer -ScriptBlock {
    msiexec.exe /i "$($args[0])" /qn
} -ArgumentList $installDirectory

In this example we still pass in the value, it just gets populated in the automatic $args variable since there is no parameter declaration. Due to you having the path quoted, we needed to use a subexpression $(...) to reference the first entry in args. Note that in this simple example with only one value passed in, $args by itself would’ve worked. However, if you pass more values, they are referenced by index. An example

Invoke-Command -ComputerName $remoteComputer -ScriptBlock {
    Write-Host the first value passed in is $args[0]
    Write-Host the second value passed in is $args[1]
} -ArgumentList 'First', 'Second'

Naturally, for the named parameters you’d specify the required variables matching the amount of arguments being passed in.

Invoke-Command -ComputerName $remoteComputer -ScriptBlock {
    Param(
        $First,
        $Second
    )
    Write-Host the first value passed in is $First
    Write-Host the second value passed in is $Second
} -ArgumentList 'First', 'Second'
4 Likes

Thanks for the reminder on that Doug :slight_smile:

Interesting, I appreciate the alternative suggestion. I didn’t realize Invoke-Command worked this way. Solution has already been found, but how do you feel about my path variables? Is there a more efficient way of defining these path variable? I feel like the script is bloated with variables, but I can’t think of a way to trim it down at the moment.

I have been teaching myself how to script, and would rather not pick up bad habits (and/or waste my time typing more than I need). :sweat_smile:

No, I thought the variables were fine. The one thing I meant to nitpick was multiple calls with invoke-command. You should put all commands in one invoke-command execution.

1 Like

Awesome, that makes sense. Thank you for your advice here!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.