Passing arguments as a variable when installing an MSI using Start-Process - powershell

I am new to Powershell and, of course, trying to learn on the fly for a project- No pressure, right! :-)
I am working on a script to run an MSI package in quiet mode, passing it an activation code as an argument, that I have to extract from an XML file.
So far, I have everything working except for getting Start-Process to run the MSI with the arguments being passed in a Variable.
Set-ExecutionPolicy Bypass -Force
[System.Xml.XmlDocument]$XML_Doc = new-object System.Xml.XmlDocument
$XML_Doc.load('c:\myfolder\Configinfo.XML')
$ActivationID = $XML_Doc.CONFIGINFO.SITEINFO.ACTIVATEID
write-host "Activation Id is: $ActivationID"
$InstallString = "`'/I C:\myfolder\myinstaller.msi akey="+'"'+$ActivationID+'"'''
#$InstallString = "`'/I C:\myfolder\myinstaller.msi akey=`"$($ActivationID)`"'"
write-host "$InstallString"'''
Start-Process msiexec.exe -ArgumentList $InstallString -Wait -NoNewWindow
#Start-Process msiexec.exe -ArgumentList '/I C:\myfolder\myinstaller.msi akey="12345678-abcd-1a1b-x9x1-a1b2c3d4e5f6"' -Wait -NoNewWindow
Above is the code I am working with now. The last line that is commented out is an activation string that works.
I have verified that $ActivationID is pulling back the correct value, and that $InstallString mirrors the argument list in the commented version of the Start-Process string.
Any help would be appreciated!

The Start-Process commands aren't necessary. PowerShell is a shell. It can run commands. Just put the commands you want to run directly in the script.
msiexec /i "C:\myfolder\myinstaller.msi" "AKEY=$ActivationID"
I quoted the parameters to msiexec.exe in case any of them contain spaces. PowerShell will automatically expand the $ActivationID variable into the string inside the double quotes.

Your ArgumentList is being passed incorrectly.
[Xml]$XML_Doc = Get-Content -Path 'C:\myfolder\Configinfo.xml'
$ActivationID = $XML_Doc.CONFIGINFO.SITEINFO.ACTIVATEID
Write-Host "Activation Id is: $ActivationID"
$Path = 'msiexec'
$ArgList = #('/i','"C:\path\file.msi"',"akey=`"$ActivationID`"")
Write-Host "$Path $ArgList"
Start-Process -FilePath $Path -ArgumentList $ArgList -Wait -NoNewWindow

First off, let me welcome you to Powershell! It's a great language and a great community gathered around a common cause.
Since you're new to the language, you can still learn new tricks and that's a good thing, because it's generally accepted that the Write-Host cmdlet is nearly always a poor choice. If you don't trust me, you should trust the inventor of Powershell.
Now that that's out of the way, we should look at your MSI command. With Powershell, we don't have to directly open msiexec, and we can call the MSI directly. I would break the path to the installer into its own variable, and then we can add all of our arguments on top of it. Also, don't forget the "/qn" switch which will actually make all of this silent. All in all, your new script will look something like this:
[System.Xml.XmlDocument]$XML_Doc = new-object System.Xml.XmlDocument
$XML_Doc.load('c:\myfolder\Configinfo.XML')
$ActivationID = $XML_Doc.CONFIGINFO.SITEINFO.ACTIVATEID
Write-Verbose "Activation Id is: $ActivationID"
$msipath = "C:\myfolder\myinstaller.msi"
$args = #("akey=$ActivationID", "/qn")
Write-Verbose "Install path is $msipath"
Write-Verbose "Activation key is $akey"
Start-Process $msipath -ArgumentList $args -Wait -NoNewWindow

Related

passing double quotes through PowerShell and msiexec

I am trying to install the Tenable Nessus agent via PowerShell script and am running into no end of issues because the Nessus MSI syntax requires a mix of no quotes and double quotes. The problem I am running into is that PowerShell needs me to escape the double quotes which seems to break the syntax understood by msiexec.exe. Let me show you what I am doing and what I have tried. I am not a strong PowerShell person, and the solution is escapes me.
Here is the code block where I set the variable with all the MSI arguments. The Nessus syntax requires that all its configs be surrounded by double quotes except for NESSUS_KEY. Earlier in the script I define the other variables.
$MSI_Arguments = #(
"/i"
"$Install_File"
"NESSUS_KEY=$Nessus_Key"
"NESSUS_SERVER=""cloud.tenable.com:443"""
"NESSUS_NAME=""$FQDN"""
"NESSUS_GROUPS=""$Nessus_Group"""
# The colon in front of a variable needs special treatment ({}).
"NESSUS_PROXY_SERVER=""${Proxy_Server}:${Proxy_Port}"""
"NESSUS_OFFLINE_INSTALL=""yes"""
"/qn"
"/norestart"
"/L*v ""$LogPath\Tenable_MSI.log"""
)
I then try running the command like so. This command does not work properly - the install proceeds, but because the parameters in -ArgumentList needs to be surrounded by double quotes, it does not get all the parameters, resulting in a broken installation.
$MSI_Command = Start-Process -Wait -NoNewWindow -PassThru -FilePath "msiexec.exe" -ArgumentList $MSI_Arguments
When I put $MSI_Arguments in double quotes, I then break how PowerShell deals with the double quotes in the variable code block. I have tried the following, but none work.
$MSI_Command = Start-Process -Wait -NoNewWindow -PassThru -FilePath "msiexec.exe" -ArgumentList "$MSI_Arguments"
$MSI_Command = Start-Process -Wait -NoNewWindow -PassThru -FilePath "msiexec.exe" -ArgumentList "$$(MSI_Arguments)"
$MSI_Command = Start-Process -Wait -NoNewWindow -PassThru -FilePath "msiexec.exe" -ArgumentList "${MSI_Arguments}"
I have even tried just running the command straight-up, no variables, just to try and figure out the syntax. I tried two double quotes (similar to what I am trying in the variable code block) and it it works.
Start-Process -Wait -NoNewWindow -PassThru -FilePath "msiexec.exe" -ArgumentList "/i C:\temp\NessusAgent-7.7.0-x64.msi NESSUS_KEY= NESSUS_SERVER=""cloud.tenable.com:443"" NESSUS_NAME=""foo"" NESSUS_GROUPS=""bar"" NESSUS_PROXY_SERVER=""proxy.company.com:80"" NESSUS_OFFLINE_INSTALL=""yes"" /qn /norestart /L*v ""C:\temp\Tenable_MSI.log"""
Now that I have a working command, I cannot figure out how to modify the variable block to work. If I just add more doubled double quotes, I get errors. If I add "" I get errors. If I add " I get errors... I am so close, yet so far.
Please, rescue me from this hell I am in. At this point I am wondering if Passing double quotes through PowerShell + WinRM has the answer, and I should base64 encode the install string, but that may be beyond my skillset given how I use variables...

How can I properly escape a space in ArgumentList of Start-Process command nested within an ArgumentList of Start-Process command?

For a long time I've been using a powershell script containing the following line to automatically start various processes as a different user AND with elevated permissions:
start-process powershell -credential <username> -argumentlist '-command &{start-process <executable.exe> -verb runas -argumentlist <c:\path\to\file\that\executable-should-open.ext>}' -workingdirectory c:\windows\system32
Formatting it this way lets me simply create a convenient shortcut to the script. This works fine... as long as <c:\path\to\file\that\executable-should-open.ext> contains no spaces. However that file is now located at a filepath which contains spaces and that's something I cannot change.
There's countless threads out there dealing with how to properly escape the -ArgumentList parameter argument(s) of Start-Process, but none of the solutions seem to work for this case where the space is in the nested -ArgumentList. I've tried for days to find the magic sauce combination of single and double quotes, backticks, backslashes, $variables, $($variables), #("arguments","as","arrays"), not using &, etc., etc.
Can anyone help me achieve this? I'm certainly open to other solutions that don't use Start-Process, but ideally the working solution should work in a standalone script, and once run, should leave no additional console windows open.
With most attempts that don't outright throw errors, the behavior I'm seeing is that c:\path\to\file\with spaces\file.ext is interpreted by the nested Start-Process cmdlet as two separate arguments, i.e. c:\path\to\file\with and spaces\file.ext.
I use this syntax to open several different administrative apps, but as a concrete example, here is the one I've been testing and failing to make work.
Original, working script:
start-process powershell -credential domain\username -argumentlist '-command &{start-process mmc -verb runas -argumentlist c:\custom-aduc.msc}' -workingdirectory c:\windows\system32`
New, non-working script:
start-process powershell -credential domain\username -argumentlist '-command &{start-process mmc -verb runas -argumentlist c:\new path\custom-aduc.msc}' -workingdirectory c:\windows\system32
For reference, the reason I can't change the path is because the custom-aduc.msc is now being kept on OneDrive, which infuriatingly provides no way to change its local sync location from drive:\path\OneDrive - CompanyName. This is a heavily supported feature request for OneDrive, but has not been committed to.
Thanks for any insights.
P.S. I've considered alternate solutions such as:
Having the script first download/copy the files to a local path without spaces, however, this prevents me from using the executable to modify the file, or I'd have to monitor changes and re-upload them.
Mounting the OneDrive path as a separate drive letter. But that's much less of a standalone solution, and seems heavy handed.
TL;DR: To answer the titular question: It might be possible, but it's much easier to simply split the Start-Process commands into different scripts, to avoid nesting one inside the quotes of the other.
After lots more trial and error with escape syntax, I was able to find an acceptable solution. However I had to split it up into two separate script files, so as to avoid nesting the second Start-Process (and thus its -ArgumentList arguments) inside any other quotes.
script1.ps1
#runas /user:domain\username "powershell -file \`"c:\script2.ps1\`""
# Equivalent to the above, but provides a nicer credential prompt:
Start-Process -FilePath powershell -Credential domain\username -WorkingDirectory "c:\windows\system32" -ArgumentList "-File `"c:\script2.ps1`""
script2.ps1
Start-Process -FilePath mmc -Verb runas -ArgumentList "`"C:\path\with spaces\aduc.msc`""
The important part is that this way I can use the usual escaping methods for each -ArgumentList, without them interfering with each other. It may still be possible to use combinations of cmd interpreter escapes (\), and posh escapes (`) to do this all in one line, but I don't have the patience to figure that out. It also may be possible to hack this into a single script, but I can't be bothered.
At this point, I can simply create shortcuts which call script1.ps1 to achieve the desired result, just like I used to.
I like to use this for at least 3 different executables, and with 3 different usernames (my average joe account, and two different superuser accounts). To avoid needing two scripts for each executable+username combination, I generalized the scripts like so:
script1.ps1
$app = $args[0]
$user = $args[1]
$script2 = "C:\path\to\script2.ps1"
# Dont run as different user if this is my usual Windows login
# Otherwise I have to enter a password for no good reason
if($user -eq "domain\non-su-account") {
& powershell -file "$script2" $app
}
else {
#runas /user:$user "powershell -file \`"$script2\`" $app"
# Equivalent to the above, but provides a nicer credential prompt:
Start-Process -FilePath powershell -Credential $user -WorkingDirectory "c:\windows\system32" -ArgumentList "-File `"$script2`" $app"
}
script2.ps1
$app = $args[0]
switch($app) {
"aduc" {
Start-Process -FilePath mmc -Verb runas -ArgumentList "`"C:\path\with spaces\aduc.msc`""
break
}
"posh" {
Start-Process -FilePath powershell -Verb runas -ArgumentList "-nologo -noexit -command `"&{. \`"C:\path\with spaces\custom-powershell-profile.ps1\`"}`""
break
}
"posh-noprofile" {
Start-Process -FilePath powershell -Verb runas -ArgumentList "-nologo -noexit"
break
}
"mecm" {
& "C:\Program Files (x86)\Microsoft Endpoint Manager\AdminConsole\bin\Microsoft.ConfigurationManagement.exe"
break
}
}
So the shortcut targets look like:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -file "C:\path\to\script1.ps1" aduc domain\username
Opening powershell and forcefully dot sourcing my custom profile script isn't really necessary, but it avoids the need to touch the powershell profiles of the superuser accounts, making this more portable.
Now that I've thoroughly demonstrated the weakness with which most markdown interpreters render nested/escaped powershell quotes, I bid you a good day. Hopefully this is useful to at least one other person ever again.

Problem using a for loop and start-process to call powershell script

Could someone please help me on this. I have created two powershell script one is suppose to call the other one lets call it script2.ps1
script2.ps1 - accepts two arguments computername and the type of remediation
example:
list=get-content c:\computer.txt
foreach ($pc in $list){
start-process powershell.exe -ArgumentList "-noexit", "-file `C:\temp\client fix\script2.ps1`", "-type install", "-computer $i"
}
The intention is for each computer in the list to execute script2.ps1 on separate process. The script runs fine if start-process is not being used example:
powershell.exe -file 'C:\temp\client fix\Script2.ps1' -type install -computer $i
The Start-Process help explains:
Specifies parameters or parameter values to use when this cmdlet starts the process. If parameters or parameter values contain a space, they need surrounded with escaped double quotes.
Your file paramater does not have an escaped double quote, but a backtick that is doing nothing.
start-process powershell.exe -ArgumentList "-noexit", "-file `"C:\temp\client fix\script2.ps1`"", "-type install", "-computer $i"
Furthermore you mix arguments for powershell.exe (-noexit, -file) with arugments for your script (-type, -computer). Also your variable $i is never assigned.
Anyways, more important is to know, that there is no reason for start-process. Simplify your script by using the call operator &.
$list=get-content c:\computer.txt
foreach ($pc in $list){
& "C:\temp\client fix\script2.ps1" -type install -computer $pc
}

Start-Process and the Stop Parsing (--%) parameter

I am having trouble getting the --% parameter to work as expected. My $TaskParams variable has the character '<' which is interpreted as a redirection by powershell, therefore needs to be escaped.
However the following does not work:
$CreateTask = Start-Process PowerShell.exe "$ScriptLocation --% $TaskParams" -Wait -PassThru
Without the --%, and when I manually remove any '<' characters, it works:
$CreateTask = Start-Process PowerShell.exe "$ScriptLocation $TaskParams" -Wait -PassThru
error received:
Start-Process : A positional parameter cannot be found that accepts argument
'--%'.
note: I am using PS 5.0
Am I using the --% parameter wrong? Any help or pointers is appreciated. Thanks
The stop-parsing symbol --% only works when calling executables directly or with the call operator &; it's not for use when calling PowerShell scripts / functions / cmdlets.
You do not need to spin up a new copy of powershell.exe or use Start-Process to run a script from within another script. Just put the script command and its parameters as a line from within the other script. For example, suppose you have script2.ps1:
param(
[String] $Name
)
Write-Host "Hello, $Name"
Now suppose you also have script1.ps1:
Write-Host "This is script1.ps1"
.\Script2.ps1 -Name "Bill Stewart"
Write-Host "script1.ps1 is finished"
If you now run script1.ps1:
PS C:\> .\Script1.ps1
This is script1.ps1
Hello, Bill Stewart
script1.ps1 is finished
If you really want to use Start-Process you could encode the argument, and run it as such. I use something similar to this when elevating past UAC:
$Code = ". '$ScriptLocation' $TaskParams"
$Encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($code))
Start-Process PowerShell.exe -ArgumentList "-EncodedCommand",$Encoded -Wait -PassThru
I'm fairly certain that would accomplish what you're looking for.

Passing parameters between Powershell scripts

I simply want to be able to pass one parameter from one PS script to another, currently my script (script1) is as follows (all thanks to user CB):
$filepath = Resolve-Path "script2.ps1"
start-process -FilePath powershell.exe -ArgumentList "-file `"$($filepath.path)`""
This succesfully opens another script in via another powershell instance. Now i want to be able to carry across a parameter to the 'script2.ps1' script. I have tried the following but this doesnt not work:
script1.ps1
$name = read-host "The name"
$filepath = Resolve-Path "script2.ps1"
start-process -FilePath powershell.exe -ArgumentList "-file `"$($filepath.path)`"-name $name"
script2.ps1
Param(
[string]$name
)
write-host $name
This should simply pass over $name from script1 into $name in script2. I think im close but not quite close enough!
Thanks for any help!
The only problem I see is that you are missing a space after the last escaped ", try this:
start-process -FilePath powershell.exe -ArgumentList "-file `"$($filepath.path)`" -name $name"
Is there a specific reason why you want to run the second script in a separate instance of Powershell? If not, you would do much better just to run the script directly:
$name = read-host "The name"
.\script2.ps1 -name $name
This way you don't have to worry about escaping any of the parameters.
The way you were doing it forces all of the parameters to be converted to strings and processed by Windows command line processing. That can be a nightmare to ensure values get through in a usable form. If instead you just invoke the script directly you can pass objects as parameters and Powershell is all about using objects rather than strings.