PowerShell Invoke with parameters not substituting PARAM for installing MSI - powershell

I am trying to use Power-shells Invoke to run an MSI install.
This code is not runing the MSI install.
param ($path1, $path2, $path3)
write-output "path1= $path1"
write-output "path2= $path2"
write-output "path3= $path3"
$PathToMSI = "D:\Install\$path1\'$path2$path3'"
write-output "PathToMSI= $PathToMSI"
$scriptblock = {Start-Process msiexec.exe -Argumentlist "/i $PathToMSI","/qn"}
invoke-command -scriptblock $scriptblock
I know that my PathToMSI is correct, as this is what is displayed, but it's not executing.
path1= 20191213.3
path2= X Y Z
path3= .msi
PathToMSI= D:\Install\20191213.3\'X Y Z.msi'
If I run it hard coded it works?
Invoke-Command -ScriptBlock {
D:\install\20191213.3\'X Y Z.msi' /quiet
}
It seems that the PathToMSI is not resolving to it's value.
I have reviewed a few like Error invoking command to install a Msi through Powershell

You need to pass $PathToMSI to the ScriptBlock. You can either use -ArgumentList for this:
Invoke-Command -ArgumentList $PathToMSI -ScriptBlock {
Start-Process msiexec.exe -Argumentlist '/i', $args[0], '/qn'
}
or you can use the $using: scope if invoking on a remote computer:
Invoke-Command -ComputerName server.domain.tld -ScriptBlock {
Start-Process msiexec.exe -Argumentlist '/i', $using:PathToMSI, '/qn'
}
Invoke-Command runs your ScriptBlock in a new PowerShell session, which doesn't know of any local variables you may have declared. The $using scope will look to the parent session for variable resolution, while -ArgumentList will pass literal variables that can be referenced using the $args variable within your ScriptBlock.

Related

Powershell: Start-Process doesn't pass arguments to cmd.exe

These are the commands run in a powershell console (Windows 10):
$username = 'Username'
$password = 'Password'
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential $username, $securePassword
Start-Process powershell.exe -Credential $credential -WindowStyle Hidden -ArgumentList "Start-Process cmd.exe -Verb RunAs -ArgumentList 'value'"
These commands work fine except that once you open cmd.exe as administrator via another user, by running this command:
echo %1
It gives me back:
%1
Literally. Instead I expect:
value
What am I doing wrong?
I just answered a question where the solution can be found using the script I provided there, with a few modifications, and invoking the chain of commands in a particular way:
RunAsProxy.ps1
# First arg should be the script path
$script = $args[0]
# Rest of args should be any script parameters
$scriptArgs = $args[1..$args.Count] -join ' '
$startProcessArgs = #{
Wait = $true
Verb = 'RunAs'
FilePath = 'cmd.exe'
ArgumentList = "/c ""$script"" $scriptArgs"
}
Start-Process #startProcessArgs
exit $LASTEXITCODE
Then call RunAsProxy.ps1 as follows as the user you want to run as, then elevate:
$command = "Command_you_want_to_run_from_cmd.exe"
$arguments = "any arguments to the program"
Start-Process -Cred $credential powershell.exe "-File ./RunAsProxy.ps1 $command $arguments"
The way this works is pretty much what you attempted, but using a pre-defined script to handle the elevation. But as you discovered you cannot call -Credential and -Verb in the same invocation on Start-Process. So this works more easily than defining the code in-line:
Run RunAsProxy.ps1 as the target user
RunAsProxy.ps1 will run cmd.exe with the provided arguments and elevate the process.
Note that if RunAsProxy.ps1 is not in the current directory you would need to provide the relative or full path to it.

Redirecting .bat output when using Invoke-Command

I am trying to redirect the output of a .bat script to a file. The script is run on another machine.
The commented line works. The t.txt file is produced in the expected location. I cannot convince PowerShell to produce the output file when the ScriptBlock is used.
The current result is that the $sb text is printed to the PowerShell console running this script. No file is produced on SERVER2. What do I need to get the output written to the file specified in the scriptblock?
$cn = 'SERVER2'
$Logfile = "D:\DBA\Scripts\monlogs\monlog_$(Get-Date -Format 'yyyy-MM-ddTHH-mm-ss').txt"
$sb = [scriptblock]::Create("{ & cmd.exe /C D:\DBA\Scripts\mon_test_001.bat >`"$Logfile`" }")
### Invoke-Command -ComputerName $cn -ScriptBlock { & D:\DBA\Scripts\mon_test_001.bat >D:\DBA\Scripts\monlogs\t.txt 2>&1 }
Invoke-Command -ComputerName $cn -ScriptBlock $sb
EDIT
After BenH's comment, I found the following to work as expected. Note that the parameter needed to have the $ escaped.
$sb = [scriptblock]::Create("param(`$Logfile) & cmd.exe /C D:\DBA\Scripts\mon_test_001.bat >`"$Logfile`"")
Rather than class create method, maybe casting would work? Then because you're running the scriptblock on a remote machine, use the "$using:" scope on the local variable. (PSv3+ onwards)
$cn = 'SERVER2'
$Logfile = "c:\temp\$(Get-Date -Format 'yyyy-MM-ddTHH-mm-ss').txt"
[scriptblock]$sb = { & cmd.exe /C c:\temp\test.bat > "$using:Logfile" }
Invoke-Command -ComputerName $cn -ScriptBlock $sb
Otherwise for earlier versions, you will need to use a param block and -ArgumentList:
[scriptblock]$sb = {param($logpath) & cmd.exe /C c:\temp\test.bat > "$logpath" }
Invoke-Command -ComputerName $cn -ScriptBlock $sb -ArgumentList $Logfile

Passing arrays as parameters in Powershell?

I have this function in PowerShell:
function Run-Process
{
param([string]$proc_path, [string[]]$args)
$process = Start-Process -FilePath $proc_path -ArgumentList $args -PassThru -Wait
$exitcode = Get-ExitCode $process
return $exitcode
}
And in some code elsewhere, I call it thusly:
$reg_exe = "C:\WINDOWS\system32\reg.exe"
$reg_args = #("load", "hklm\$user", "$users_dir\$user\NTUSER.DAT")
$reg_exitcode = Run-Process -proc_path $reg_exe -args $reg_args
When it's called, $proc_path gets the value for $reg_exe, but $args is blank.
This is how array parameters are passed in Powershell, isn't it?
$args is a special (automatic) variable in PowerShell, don't use it for your parameter name.
-ArgumentList is the typical name given to this type of parameter in PowerShell and you should stick to the convention. You could give it an alias of args and then you could call it the way you like without conflicting with the variable:
function Run-Process {
[CmdletBinding()]
param(
[string]
$proc_path ,
[Alias('args')]
[string[]]
$ArgumentList
)
$process = Start-Process -FilePath $proc_path -ArgumentList $ArgumentList -PassThru -Wait
$exitcode = Get-ExitCode $process
return $exitcode
}
A possible alternative, that may work if you absolutely must name the parameter as args (untested):
function Run-Process
{
param([string]$proc_path, [string[]]$args)
$process = Start-Process -FilePath $proc_path -ArgumentList $PSBoundParameters['args'] -PassThru -Wait
$exitcode = Get-ExitCode $process
return $exitcode
}
Please don't do this though; the other workaround is better.

PowerShell Invoke-Command Argumentlist

I'm struggling to pass my arguments to my custom made function.
This works fine:
Delete-OldFiles -Target $Target -OlderThanDays $OlderThanDays -LogName Auto_Clean.log
These don't work at all:
# (using -ScriptBlock ${Function:Delete-OldFiles} for all calls)
Invoke-Command -ArgumentList ( ".$Target", ".$OlderThanDays", ".$LogName Auto_Clean.log")
Invoke-Command -ArgumentList #("$Target", "$OlderThanDays", "Auto_Clean.log")
Invoke-Command -ArgumentList #({-OlderThanDays "10", -Target "E:\Share\Dir1", -LogName "Auto_Clean.log"})
Invoke-Command -ArgumentList (,#('$Target','$OlderThanDays','Auto_Clean.log'))
Can you help me on how to pass the following parameters correctly:
Param(
[Parameter(Mandatory=$False,Position=1)]
[String]$Server,
[Parameter(Mandatory=$True,Position=2)]
[ValidateScript({Test-Path $_})]
[String]$Target,
[Parameter(Mandatory=$True,Position=3)]
[Int]$OlderThanDays,
[Parameter(Mandatory=$True,Position=4)]
[String]$LogName,
[switch]$CleanFolders
)
When you use Invoke-Command -ArgumentList arguments are passed to script block as positional parameters. Looking at you param() block: it was not designed with using positional parameters in mind... Having optional parameters first, and mandatory after means you always need to provide optional parameter, or use named parameters for all mandatory ones.
In other words: having optional parameter in the first position will stop you from running this code in this fashion with Invoke-Command.
To test your code locally you should try it this way:
Delete-OldFiles $Target $OlderThanDays "Auto_Clean.log"
I would expect it to prompt you for LogName parameter.

How to Invoke-Command and pass path to command as parameter

I'm tearing my hair out trying to invoke-command but pass the path to the exe as a parameter
eg:
I want to take this command
powershell Invoke-Command -ComputerName localhost -ScriptBlock { param($command ) C:\windows\system32\getmac.exe /$command } -ArgumentList ?
and translate it into a form like this
powershell Invoke-Command -ComputerName localhost -ScriptBlock { param($path, $command ) $path\getmac.exe /$command } -ArgumentList C:\windows\system32,?
I've tried all manner of quoting, ampersands and other contortions but can't get it to work. The above attempt results in
Unexpected token '\getmac.exe' in expression or statement.
At line:1 char:97
(I don't really want to invoke getmac on localhost, this is the runnable, SO distilled version)
Try this option. It shows me help for cscript.exe.
C:\>powershell.exe Invoke-Command -ComputerName localhost -ScriptBlock { param($path, $command ) cmd /c $path $command } -args '"C:\windows\system32\cscript.exe"','"/?"'
I tried other options using & and then path and arguments and it was giving me missing } exception. Then using cmd /c instead of & inside scriptblock fixed the issue.
Powershell won't parse a string as a command that way. For e.g. if you do this:
$path="C:\Windows\System32"
$path\getmac.exe
You would get the same error. The trick to work around this is to use the invoke operator &:
&$path\getmac.exe
or in your example, like this (also note that for a command that you pass to the powershell executable, you must wrap it in scriptblock braces):
powershell -command {Invoke-Command -ComputerName localhost -ScriptBlock { param($path, $command ) &$path\getmac.exe /$command } -ArgumentList C:\windows\system32,?}