Start-Process and powershell.exe with splatting - powershell

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

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

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.

How to run Command as a different user with Powershell?

I'm trying to make a script that changes the HostnameAlias for a given dns record.
But only certain users have access to editing these records, for example ADMIN can edit it but CURRENTUSER cannot.
Currently I have this piece of code:
param(
[ValidateNotNull()]
[System.Management.Automation.PSCredential]
$Credential = $(Get-Credential)
)
$Command = "Set-DnsServerResourceRecord -NewInputObject $($NewObject) -OldInputObject $($OldObject) -ZoneName $($ZoneName)"
Start-Process -FilePath PowerShell -NoNewWindow -Credential $Credential -ArgumentList $Command
But i just keep getting Start-Process : This command cannot be run due to the error: The user name or password is incorrect even though I am absolutely sure they are indeed correct.
What am I doing wrong here.
Ps, I have looked at all the related questions, none seem to answer my question.
You can call System.Management.Automation.PSCredential object to specify any credentials you want and run with it in any process
$User = 'yourdomain\youruser'
$Password = 'yourpassword'
$Secure_Password = ConvertTo-SecureString $Password -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential($User, $Secure_Password)
$Command = "Set-DnsServerResourceRecord -NewInputObject $($NewObject) -OldInputObject $($OldObject) -ZoneName $($ZoneName)"
Start-Process -FilePath PowerShell -NoNewWindow -Credential $Credential -ArgumentList $Command
You can use this:
#Get User credential
$Credential = Get-Credential Domain\UserNameYouWant
#Use System.Diagnostics to start the process as User
$ProcessInfo = New-Object System.Diagnostics.ProcessStartInfo
#With FileName we're basically telling powershell to run another powershell process
$ProcessInfo.FileName = "powershell.exe"
#CreateNoWindow helps avoiding a second window to appear whilst the process runs
$ProcessInfo.CreateNoWindow = $true
#Note the line below contains the Working Directory where the script will start from
$ProcessInfo.WorkingDirectory = $env:windir
$ProcessInfo.RedirectStandardError = $true
$ProcessInfo.RedirectStandardOutput = $true
$ProcessInfo.UseShellExecute = $false
#The line below is basically the command you want to run and it's passed as text, as an argument
$ProcessInfo.Arguments = "The command you want"
#The next 3 lines are the credential for User as you can see, we can't just pass $Credential
$ProcessInfo.Username = $Credential.GetNetworkCredential().username
$ProcessInfo.Domain = $Credential.GetNetworkCredential().Domain
$ProcessInfo.Password = $Credential.Password
#Finally start the process and wait for it to finish
$Process = New-Object System.Diagnostics.Process
$Process.StartInfo = $ProcessInfo
$Process.Start() | Out-Null
$Process.WaitForExit()
#Grab the output
$GetProcessResult = $Process.StandardOutput.ReadToEnd()
# Print the Job results
$GetProcessResult
Just a mistake on my part, forgot to specify domain before username when entering credentials.
Can solve it like this Get-Credential Domain\

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

How to run a program as another user and add arguments in powershell?

We have a program that only updates when being run with the switch /t from an administrator account.
I came up with the CMD prompt version, but I'm new to powershell and having a hard time translating it to Powershell.
The CMD version is:
C:\Windows\System32\runas.exe /savecred /user:ourdomain\ouruseracct "C:\Program Files (x86)\ProjectMatrix\ProjectNotify\ProjectNotify.exe /t"
So far I got:
C:\Windows\System32\runas.exe /user:ourdomain\ouruseracct /savecred "powershell -c start-process -FilePath \"'C:\Program Files (x86)\ProjectMatrix\ProjectNotify\ProjectNotify.exe'\" -verb runAs"
Which runs powershell as admin and starts the program as admin but we need to pass the argument -t or /t to projectnotify.exe when running it.
I believe we need to make use of the -argumentlist but not sure how to word it.
I tried
$t = "-t"
Start-Process -FilePath "C:\Program Files (x86)\ProjectMatrix\ProjectNotify\projectnotify.exe" -ArgumentList $t -Verb runas
Which runs the program but not sure if that's how you pass the argument.
Extra work (troubleshooting):
$Cred = Get-Credential
$ProcInfo = New-Object -TypeName 'System.Diagnostics.ProcessStartInfo'
$ProcInfo.Domain = $Cred.GetNetworkCredential().Domain
$ProcInfo.UserName = $Cred.UserName
$ProcInfo.Password = $Cred.Password
$ProcInfo.FileName = "${Env:ProgramFiles(x86)}\ProjectMatrix\ProjectNotify\ProjectNotify.exe"
$ProcInfo.Arguments = '/t'
$ProcInfo.WorkingDirectory = "${Env:ProgramFiles(x86)}\ProjectMatrix\ProjectNotify"
$ProcInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Normal
$ProcInfo.Verb = 'RunAs'
$ProcInfo.UseShellExecute = $true
[System.Diagnostics.Process]::Start($ProcInfo)
After some more thought, here's a simpler way (in a single command even):
Start-Job -Credential (Get-Credential) -ScriptBlock {
$Dir = "${Env:ProgramFiles(x86)}\ProjectMatrix\ProjectNotify"
$StartArgs = #{
'FilePath' = "$Dir\ProjectNotify.exe"
'ArgumentList' = '/t'
'Verb' = 'RunAs'
'WindowStyle' = 'Normal'
'WorkingDirectory' = $Dir
'PassThru' = $true
}
Start-Process #StartArgs
} | Wait-Job | Receive-Job
My previous answer is at the bottom of this post now.
References:
about_Splatting
Get-Credential
Start-Process
Start-Job
Extra reading:
Import-CliXml
Export-CliXml
Assuming an on-demand script, you should create a pscredential object if you want to natively run this from powershell:
Launch.cmd
SET "PS=%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe"
SET "SCRIPT=%SYSTEMDRIVE%\Path\to\wrapper.ps1"
%PS% -NoProfile -NoLogo -ExecutionPolicy Bypass -File "%SCRIPT%"
wrapper.ps1
$Cred = Get-Credential
# To avoid prompting every time:
#
# if (-not (Test-Path -Path '.\mycred.xml')) {
# Get-Credential | Export-CliXml -Path '.\mycred.xml'
# }
# $Cred = Import-CliXml -Path '.\mycred.xml'
$StartArgs = #{
'FilePath' = "$PSHOME\powershell.exe"
'ArgumentList' = '-NoProfile', '-NoLogo', '-File', '.\runas.ps1'
'Credential' = $Cred
}
Start-Process #StartArgs
runas.ps1
$StartArgs = #{
'FilePath' = "${Env:ProgramFiles(x86)}\ProjectMatrix\ProjectNotify\ProjectNotify.exe"
'ArgumentList' = '/t'
'Verb' = 'RunAs'
}
Start-Process #StartArgs
I know the question asks for arguements, but if you don't them, this works:
Start cmd.exe -Verb RunAs
You can also run this using the 'Run' window or search box:
powershell -command start cmd.exe -verb runas