Powershell referencing current directory and subfolder - powershell

I use the following code to reference files in the current script directory
$PSScriptRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
Then I can call it like
try {
WriteLog("Installing...")
$installresult = (Start-Process msiexec.exe -ArgumentList "/i
$PSScriptRoot\InstallPrism6.msi /qn /norestart" -Wait -PassThru).ExitCode
WriteLog("Installation finished with return code: $installresult")
}
catch {
WriteLog($_.Exception.Message)
}
This works fine. However, if I want to reference a file in a subdirectory like so
try {
WriteLog("Installing...")
$installresult = (Start-Process msiexec.exe -ArgumentList "/i $PSScriptRoot
+ \test\InstallPrism6.msi /qn /norestart" -Wait -PassThru).ExitCode
WriteLog("Installation finished with return code: $installresult")
}
catch {
WriteLog($_.Exception.Message)
}
it fails with error code 1639.
If this doesn't work, how can I reference subdirectories when using $PSScriptRoot?

Note that the $PSScriptRoot variable is predefined on PowerShell 3.0 and newer, so you only need that variable if it's not already defined. I believe the proper syntax for what you want to do should look like the following:
if ( -not $PSScriptRoot ) {
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
}
try {
WriteLog "Installing..."
$installresult = (Start-Process msiexec.exe -ArgumentList "/i","`"$PSScriptRoot\test\InstallPrism6.msi`"","/qn","/norestart" -Wait -PassThru).ExitCode
WriteLog "Installation finished with return code: $installresult"
}
catch {
WriteLog $_.Exception.Message
}
-ArgumentList is technically an array, and I embedded the " in case the path contains any whitespace.

Try not leaving spaces in your path. When you are building a string using a variable, you don't need to concatenate. It's not about referencing subfolders.
try {
WriteLog("Installing...")
$installresult = (Start-Process msiexec.exe -ArgumentList "/i ${PSScriptRoot}\test\InstallPrism6.msi /qn /norestart" -Wait -PassThru).ExitCode
WriteLog("Installation finished with return code: $installresult")
}
catch {
WriteLog($_.Exception.Message)
}

Related

How to pass arguments to MSI file with PowerShell

I've read that you can pass arguments to a .msi file, but I have no idea how to do it correctly. I've tried the following, where $ArgumentList is an array.
$ArgumentList = #("/i .\NSClient v67.msi", "/norestart", "/quiet", "/l*v '$directory'", "token=$token", "host=$_host", "mode=$mode")
Start-Process "msiexec" -ArgumentList $ArgumentList -Wait -NoNewWindow
This is part of my script, where I'm trying to install NetSkope on my machine by executing a command.
In theory, the command should look like msiexec /i "NSClient v67.msi" token=loremipsum host=bryan.goskope.com mode=peruserconfig /norestart /quiet /l*v "C:\Temp\NetskopeInstallation.log.
#Find file path
$rawPath = Invoke-Expression -Command 'C:\Windows\System32\WHERE /r C:\Users\ /f NSClient*.msi'
#Extract the directory
$filePath = Invoke-Expression -Command "cmd.exe --% /c FOR /f ""tokens=1"" %A IN ($rawPath) DO (ECHO
'%~dpA')"
#Cast $filePath to work with string methods
$filePath = Out-String -InputObject $filePath
$filePath = $filePath.split("'")[1]
Invoke-Expression -Command "cmd.exe --% /c cd $filePath"
$ArgumentList = #("/i .\NSClient v67.msi", "/norestart", "/quiet", "/l*v '$directory'",
"token=$token", "host=$_host", "mode=$mode")
Start-Process "msiexec" -ArgumentList $ArgumentList -Wait -NoNewWindow
I would also recommend using the Powershell MSI Module
Concerning Start-Process:
-Argumentlist expects string as a type. I don't think you can just pass an array.
You also need to surround the parameters that require a space with escaped double quotes. The escape character is powershell is the grave-accent(`).
Another problem is that the variable $directory will never be expanded, because it is surrounded with single quotes. You need to remove those.
The following should work for your example, but I personally would just remove the space in the file name, since you don't need to do weird stuff with escaping.
Without escaping:
$ArgumentList = "/i .\NSClientv67.msi /norestart /quiet /l*v $directory token=$token host=$_host mode=$mode"
With escaping:
$ArgumentList = "/i `".\NSClient v67.msi`" /norestart /quiet /l*v $directory token=$token host=$_host mode=$mode"
Here's a slightly different syntax:
$MSIArguments = #(
"/x"
"`"C:\path with spaces\test.msi`""
"/qb"
"/norestart"
"/l*v"
"`"C:\path with spaces\test.log`""
)
Start-Process "msiexec.exe" -ArgumentList $MSIArguments -Wait -NoNewWindow

msiexec with -Wait installation continuously failing

There is my script:
try {
Start-Process "C:\tmp\_deployment\AcroRdrDC1900820071_hu_HU.exe" -argumentlist '/sALL /EULA_ACCEPT=YES /qn"' -PassThru -Wait
} catch {
Write-Host "mypatch.exe returned the following error $_"
Throw "Aborted mypatch.exe returned $_"
}
try {
Start-Process "C:\tmp\_deployment\Office 2016 Prof Plus 64bit HUN\setup.exe" -Verb runAs -PassThru -Wait
} catch {
Write-Host "mypatch.exe returned the following error $_"
Throw "Aborted mypatch.exe returned $_"
}
try {
Start-Process "C:\tmp\_deployment\jre-8u181-windows-x64.exe" -ArgumentList '/s INSTALL_SILENT=1 STATIC=0 AUTO_UPDATE=0 WEB_JAVA=1 WEB_JAVA_SECURITY_LEVEL=H WEB_ANALYTICS=0 EULA=0 REBOOT=0 NOSTARTMENU=0 SPONSORS=0' -PassThru -Wait
} catch {
Write-Host "mypatch.exe returned the following error $_"
Throw "Aborted mypatch.exe returned $_"
}
Start-Process msiexec.exe -PassThru -Wait -ArgumentList '/I C:\tmp\_deployment\7z1805-x64.msi /passive'
Start-Process msiexec.exe -PassThru -Wait -ArgumentList '/I C:\tmp\_deployment\tightvnc-2.8.11-gpl-setup-64bit.msi /quiet /norestart'
Everything is fine until the 7zip installation is complete. When it's done the next process - tightvnc - not installing to the computer.Everything is fine until the 7zip installation finishes. When the next process - tightvnc - starts, the installation will exit immediately and tightvnc not available on the machine.
I tried to change the last lines with this:
msiexec.exe /I C:\tmp\_deployment\tightvnc-2.8.11-gpl-setup-64bit.msi /quiet /norestart -Wait
But nothing.
If I run the the last lines alone, tightnc install will be successful.
I think the -Wait parameters not working. Any idea how can I solve this?
If anyone knows how to simplify this script, I would appreciate it!
Thanks for the help.

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 to display Regsvr32 result in console instead of dialog

I've searched but did not find any answer.
The task is register one dll using Powershell ps1, followed by other lines of scripts. I don't want to be interrupted by the dialog, so added the /s parameter. But now the result information is ignored, no matter succeed or fail.
I want the result displayed in console. But how?
Launch regsvr32.exe /s with Start-Process -PassThru and inspect the ExitCode property:
$regsvrp = Start-Process regsvr32.exe -ArgumentList "/s C:\path\to\your.dll" -PassThru
$regsvrp.WaitForExit(5000) # Wait (up to) 5 seconds
if($regsvrp.ExitCode -ne 0)
{
Write-Warning "regsvr32 exited with error $($regsvrp.ExitCode)"
}
Here is a more complete full powershell cmdlet with pipeline support.
function Register-Dll
{
<#
.SYNOPSIS
A function that uses the utility regsvr32.exe utility to register a file
.PARAMETER Path
The file path
.PARAMETER Unregister
when specified, unregisters instead of registers
#>
[CmdletBinding()]
param (
[ValidateScript({ Test-Path -Path $_ -PathType 'Leaf' })]
[Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipeLineByPropertyName=$true)]
[Alias("FullName")]
[string[]]$Path,
[Alias("u")]
[switch]$Unregister
)
begin {
if ($Unregister)
{
$regflag = "-u "
}
else
{
$regflag = ""
}
[int]$NumFailed=0
$RegExitCodes = #{
0="SUCCESS";
1="FAIL_ARGS - Invalid Argument";
2="FAIL_OLE - OleInitialize Failed";
3="FAIL_LOAD - LoadLibrary Failed";
4="FAIL_ENTRY - GetProcAddress failed";
5="FAIL_REG - DllRegisterServer or DllUnregisterServer failed.";
}
}
process {
foreach ($p in $path)
{
try
{
$regsvrp = Start-Process regsvr32.exe -ArgumentList "/s $regflag <code>$p</code>" -Wait -NoNewWindow -PassThru
if($regsvrp.ExitCode -ne 0)
{
$NumFailed++
Write-Error "regsvr32 $regflag for $p exited with error $($regsvrp.ExitCode) - $($RegExitCodes[$regsvrp.ExitCode])"
}
} catch {
$NumFailed++
Write-Error $_.Exception.Message
}
}
}
end {
if ($NumFailed -gt 0)
{
if ($Unregister)
{
$mode = "unregister"
}
else
{
$mode = "register"
}
Write-Error "Failed to $mode $NumFailed dll's, see previous errors for detail"
}
}
}
Usage:
function Register-MyAppDll
{
param(
[Parameter(Mandatory=$true,ParameterSetName="Both")]
[switch]$ReRegister,
[Parameter(Mandatory=$true,ParameterSetName="UnregisterOnly")]
[Alias("u")]
[switch]$UnRegister,
[Parameter(Mandatory=$true,ParameterSetName="RegisterOnly")]
[Alias("r")]
[switch]$Register
)
$RegOptions = #()
if ($UnRegister -or $ReRegister) { $RegOptions += #{Unregister=$true} }
if ($Register -or $ReRegister) { $RegOptions += #{} }
$dlltoregister = Get-ChildItem "C:\MyApp\bin" -Filter *.dll | where {$_ -notmatch '^interop'}
foreach ($RegOpt in $RegOptions)
{
$dlltoregister | Register-Dll #RegOpt
}
}
Register-MyAppDll -UnRegister
Register-MyAppDll -Register
Register-MyAppDll -ReRegister
Enjoy :)
Thank you Justin! I'm using this script and it works great.
There seems to be a typo in the following line of code:
$regsvrp = Start-Process regsvr32.exe -ArgumentList "/s $regflag <code>$p</code>" -Wait -NoNewWindow -PassThru
The code tag shoudn't be there. I changed it to the following with added escaped double quotes around path to support spaces in paths:
$regsvrp = Start-Process regsvr32.exe -ArgumentList "/s $regflag `"$p`"" -Wait -NoNewWindow -PassThru

How to get output from powershell process

Here's the code
function RunPowershellAsAdmin($CommandToBeExecuted)
{
If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
{
#$arguments = "& '" + $myinvocation.mycommand.definition + "'"
Start-Process powershell -Verb runAs -ArgumentList "$CommandToBeExecuted" -Verbose
}
}
RunPowershellAsAdmin("& { Import-Module WebAdministration; if(Test-Path 'IIS:\Sites\$Website_Name') { Remove-WebSite -Name '$Website_Name'; } }")
-Verb and -RedirectStandardOutput are not in the same parameter, so i can not use -RedirectStandardOutput to get process output as per answer from this link.
I want to run the process in a hidden window, wait for it to return and get the error, output and exit code.
Is there any other solution?
Thanks in advance.
If you need access to both RunAs in addition to redirecting any of the standard streams, you'll need to use the System.Diagnostics.Process and System.Diagnostics.ProcessStartInfo classes directly. More information on how to handle the redirected stream can be found on MSDN.
$startInfo = new-object System.Diagnostics.ProcessStartInfo
$startInfo.FileName = "powershell"
$startInfo.Arguments = "& { Import-Module WebAdministration; ... }"
$startInfo.Verb = "runas"
$startInfo.RedirectStandardOutput = $true
$process = [System.Diagnostics.Process]::Start($startInfo)
$output = $process.StandardOutput.ReadToEnd()
$process.WaitForExit()