Passing arrays as parameters in Powershell? - 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.

Related

Elevate a script with parameters

I have a Powershell script with parameters that I'd like to be able to self-elevate.
[CmdletBinding()]
param (
[Parameter(ParameterSetName="cp")]
[Switch]
$copy = $false,
[Parameter(ParameterSetName="mv")]
[Switch]
$move = $false
)
# Elevate if required
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) {
$Cmd = (
'-File',
"`"$($MyInvocation.MyCommand.Path)`"",
$MyInvocation.BoundParameters
)
$ProcArgs = #{
FilePath = 'PowerShell.exe'
Verb = 'RunAs'
ArgumentList = $Cmd
}
Start-Process #ProcArgs
Exit
}
}
Set-Location -LiteralPath $PSScriptRoot
Write-Host "$copy"
Pause
If I comment out the param block and run script.ps1 -copy, an elevated Powershell window opens and prints out Press enter to continue, i.e. it works.
If I comment out the if statement, the current window outputs True, i.e. it also works.
If I run the whole thing though, the elevated windows opens for a split second, then closes ignoring Pause with no output anywhere.
I want the elevated window to open and print out True.
I tested this on Linux and worked for me but couldn't test on Windows, I don't see other way around having to manipulate the $PSBoundParameters into strings to pass the arguments on -ArgumentList.
Below code is meant to be exclusively for testing, hence why I've removed the if conditions.
[CmdletBinding()]
param (
[Parameter(ParameterSetName="cp")]
[Switch]$copy,
[Parameter(ParameterSetName="mv")]
[Switch]$move
)
$argument = #(
"-File $PSCommandPath"
"-$($PSBoundParameters.Keys)"
)
$ProcArgs = #{
FilePath = 'powershell.exe'
Verb = 'RunAs'
ArgumentList = $argument
}
Start-Process #ProcArgs
"Started new Process with the argument: -$($PSBoundParameters.Keys)"
[System.Console]::ReadKey()
Exit

Start-Process and powershell.exe with splatting

I've been trying for a couple of days now to multi-thread a WPF GUI which will run a PS3.0 script once the button has been clicked. I cannot use start-job as that I would have to track (multiple sessions at once), however, I would like to just run the script in a separate process of PS- as if I were to open multiple instances of the script from a shortcut. And be able to just have an open PS window which will track the progress within the script itself.
Expected results would be starting a script in powershell.exe session and passing 3 arguments - 2 strings and 1 boolean value. Which are provided by the user.
So in ISE:
C:\temp\test.ps1 -argumentlist $computername $username $citrixtest
Works fine.
I've spent a few hours scouring through the internet only to find a thread where a start-job was recommended or a way to use a background worker- this is not what I want from the script.
So I would guess the invocation from a button click would be something of the like (some of the things I have tried)
$ComputerName = "testtext1"
$UserName = "testtext2"
$CitrixTest = $True
$command = "c:\temp\test.ps1"
$arg = #{
Computername = "$computername";
Username = "$username";
CitrixTest = "$citrixtest"
}
#$WPFStartButton.Add_Click({
Start-Process powershell -ArgumentList "-noexit -command & {$command} -argumentlist $arg"
#})
Does not pass arguments to test.ps1- it is, however, getting to the "pause" - so the script successfully launches.
Where test.ps1 is
$ComputerName
$UserName
$CitrixTest
pause
Caller:
function Caller {
Param (
$ScriptPath = "c:\temp\test.ps1"
)
$Arguments = #()
$Arguments += "-computername $ComputerName"
$Arguments += "-UserName $UserName"
$Arguments += "-citrixtest $citrixtest"
$StartParams = #{
ArgumentList = "-File ""$ScriptPath""" + $Arguments
}
Start-Process powershell #StartParams
}
Caller
Does not start the script altogether- PS window just closes- possibly a path to .ps1 script not being found.
And a different approach which also nets in the script starts but not passing the arguments
$scriptFile = '"C:\temp\test.ps1"'
[string[]]$argumentList = "-file"
$argumentList += $scriptFile
$argumentlist += $computername
$argumentlist += $UserName
$argumentlist += $CitrixTest
$start_Process_info = New-Object System.Diagnostics.ProcessStartInfo
$start_Process_info.FileName = "$PSHOME\PowerShell.exe"
$start_Process_info.Arguments = $argumentList
$newProcess = New-Object System.Diagnostics.Process
$newProcess.StartInfo = $start_Process_info
$newProcess.Start() | Out-Null
Is there a way to make this work as I want it to? Or should I just dig deeper into runspaces and try with that?
#Bill_Stewart I just realized I did not put the param(args) in my script...
And that's why it would not pull those variables as I would like them to. I will have to check when I'm back in the office if it's just that what I was missing.
Checked on my laptop that's running PS 5.1 and this seems to be working as intended
$testarg = #(
'-File'
"C:\temp\test.ps1"
"$computername"
"$username"
"$citrixtest"
)
Start-Process powershell.exe -ArgumentList $testarg
Where test.ps1 is:
param(
$ComputerName,
$UserName,
$citrixtest
)
$ComputerName
$UserName
$CitrixTest
pause

How do I pass multiple paramaters to PowerShell Start-Job to run a process in the background?

I know there are many examples out there that are similar to mine. I have tried many with no real success. I simplified my code to the basic issue in the hopes that someone could point me in the right direction.
function Send-DBBackupToS3
{
param(
[Parameter(Mandatory=$true)][string]$p1,
[Parameter(Mandatory=$true)][string]$p2,
[Parameter(Mandatory=$true)][string]$p3
)
try
{
Write-Host "starting process..."
$TransferAppExe = $p1
$arguments = '-OnDiskPath', $p2, '-NotificationEmailAddress', $p3
$ps = Start-Process -FilePath $TransferAppExe -ArgumentList $arguments -Wait -PassThru
}
catch
{
# get error record
[Exception]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]#{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info
}
}
function Start-DBCopyAndTransfer
{
param(
[Parameter(Mandatory)]
[string]$AppPath,
[Parameter(Mandatory)]
[string]$UploadFilePath,
[Parameter(Mandatory)]
[string[]]$EmailAddress
)
Write-Host "calling job..."
Start-Job -Name Send2S3 -ScriptBlock {param($p1, $p2, $p3) Send-DBBackupToS3 -p1 $p1 -p2 $p2 -p3 $p3} -ArgumentList $AppPath,$UploadFilePath,$EmailAddress
}
Clear-Host
Write-Host "calling function..."
Start-DBCopyAndTransfer -AppPath "C:\FileToS3.exe" -UploadFilePath "C:\chron.cti" -EmailAddress "4321#gmail.com"
I am trying to pass into a Start-Process cmdlet the parameters needed to run the .exe.
The results I are as follows:
calling function...
calling job...
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
1 Send2S3 BackgroundJob Running True localhost param($p1, $p2, $p3) S...
PS C:\WINDOWS\system32> Get-Job
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
1 Send2S3 BackgroundJob Failed False localhost param($p1, $p2, $p3) S...
PS C:\WINDOWS\system32>
I never see the Write-Host "starting process..." fire. If I take away the params(Hard code the values) from Send-DBBackupToS3 it works just fine. Thanks for your time!
I hate to answer my own questions...However, if someone runs across this I want them to have the solution.
$func = {
function Send-DBBackupToS3
{
param(
[Parameter(Mandatory=$true)][string]$p1,
[Parameter(Mandatory=$true)][string]$p2,
[Parameter(Mandatory=$true)][string]$p3
)
try
{
Write-Host "starting process..."
$TransferAppExe = $p1
$arguments = '-OnDiskPath', $p2, '-NotificationEmailAddress', $p3
Start-Process -FilePath $TransferAppExe -ArgumentList $arguments -Wait -PassThru
}
catch
{
# get error record
[Exception]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]#{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
}
# output information. Post-process collected info, and log info (optional)
$info
}
}
}
function Start-DBCopyAndTransfer
{
param(
[Parameter(Mandatory)]
[string]$AppPath,
[Parameter(Mandatory)]
[string]$UploadFilePath,
[Parameter(Mandatory)]
[string[]]$EmailAddress
)
Write-Host "calling job..."
$job = Start-Job -Name Send2S3 -InitializationScript $func -ScriptBlock {param($p1, $p2, $p3) Send-DBBackupToS3 -p1 $p1 -p2 $p2 -p3 $p3} -ArgumentList $AppPath,$UploadFilePath,$EmailAddress
Receive-Job -Job $job
Write-Host ('State: {0}' -f $job.State)
}
Clear-Host
Write-Host "calling function..."
Start-DBCopyAndTransfer -AppPath "C:\FileToS3.exe" -UploadFilePath "C:\chron.cti" -EmailAddress "4321#gmail.com"
You can pass multiple arguments as follows:
Start-Process -FilePath $TransferAppExe -ArgumentList $argument1, $argument2, $argument3 -Wait -PassThru
Or pass a list of arguments:
$arguments = $argument1, $argument2, $argument3
Start-Process -FilePath $TransferAppExe -ArgumentList $arguments -Wait -PassThru
Or pass a single string with all arguments:
Start-Process -FilePath $TransferAppExe -ArgumentList "$argument1 $argument2 $argument3" -Wait -PassThru
Also, you don't need to WaitForExit if you have the -Wait argument, as they do the same thing.

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()

Powershell start-process -wait parameter fails in remote script block

So here's a sample of what I'm trying to do:
Invoke-Command [Connection Info] -ScriptBlock {
param (
[various parameters]
)
Start-Process [some .exe] -Wait
} -ArgumentList [various parameters]
It connects to the other machine just fine, and launches the process fine. The problem is it doesn't wait for the process to complete before moving on. This causes issues. Any ideas?
Quick edit: why does the -Wait parameter fail when running the process remotely?
I ran into this once before, and IIRC, the workaround was:
Invoke-Command [Connection Info] -ScriptBlock {
param (
[various parameters]
)
$process = Start-Process [some .exe] -Wait -Passthru
do {Start-Sleep -Seconds 1 }
until ($Process.HasExited)
} -ArgumentList [various parameters]
This is the problem with Powershell version 3, but not version 2 where -Wait works as it should.
In Powershell 3 .WaitForExit() does the trick for me:
$p = Start-Process [some .exe] -Wait -Passthru
$p.WaitForExit()
if ($p.ExitCode -ne 0) {
throw "failed"
}
Just Start-Sleep until .HasExited - doesn't set .ExitCode, and it's usually good to know how your .exe finished.
You can also work around this by using the System.Diagnostics.Process class. If you do not care about the output you can just use:
Invoke-Command [Connection Info] -ScriptBlock {
$psi = new-object System.Diagnostics.ProcessStartInfo
$psi.FileName = "powershell.exe"
$psi.Arguments = "dir c:\windows\fonts"
$proc = [System.Diagnostics.Process]::Start($psi)
$proc.WaitForExit()
}
If you do care you can do something similar to the following:
Invoke-Command [Connection Info] -ScriptBlock {
$psi = new-object System.Diagnostics.ProcessStartInfo
$psi.FileName = "powershell.exe"
$psi.Arguments = "dir c:\windows\fonts"
$psi.UseShellExecute = $false
$psi.RedirectStandardOutput = $true
$proc = [System.Diagnostics.Process]::Start($psi)
$proc.StandardOutput.ReadToEnd()
}
This will wait for the process to complete and then return the standard output stream.