How To Capture Output from Non-elevated Process Run from Elevated Powershell - powershell

I'm trying to update an elevated PowerShell script that's using StartProcess on a BAT file that runs RunAs on PowerShell.exe to run another PowerShell script without elevation in order to clone a git repository so that the directory is created in a way that a normal non-elevated user will be able to use.
Elevated PS1: Start-Process
=> Elevated .BAT: RunAs /trustlevel:0x20000
=> Non-elevated PS1
This is failing in some environments and I can't figure out why so I'm trying to figure out how to capture stdout and stderr from all levels of this process, but I'm not seeing the error or any output. I can capture it down to the BAT file level, but I can't seem to see anything that's happening within the inner-most Powershell script.
This seems like an awful lot of work just to programmatically clone a Git repository from an elevated process. Is there a way to make this work or is there an easier way?
EDIT: Just learned that this solution was broken as of Windows 11 Update 22H2: https://superuser.com/questions/1749696/parameter-is-incorrect-when-using-runas-with-trustlevel-after-windows-11-22h2
but the workaround is to use the /machine switch when running RunAs.

I suggest simplifying your approach as follows:
Use synchronous invocation of runas.exe, via Start-Process -Wait, which obviates the need for an intermediate batch file, and the need for a named pipe (System.IO.Pipes.NamedPipeClientStream)
Let the runas.exe-launched PowerShell child process that runs test2.ps1 capture that script's output in a temporary file, which you can read after the Start-Process -Wait call returns.
test2.ps1 can then just produce output normally - no need for System.IO.Pipes.NamedPipeClientStream
Elevated PowerShell Script (test.ps1):
function IsAdmin{
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$Is64 = [Environment]::Is64BitOperatingSystem
if ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Output "Running with elevated privileges. (64-bit=$Is64)"
} else {
Write-Output "Running without elevated privileges. (64-bit=$Is64)"
}
}
IsAdmin
# Create a temporary file in which to capture the output from the
# PowerShell child process launched by runas.exe.
$outFile = New-TemporaryFile
# Use Start-Process -Wait to directly invoke runas.exe,
# which doesn't just wait for runas.exe ITSELF to exit, but also
# waits for its CHILD processes.
# This ensures that execution is blocked until the other PowerShell script exits too.
Start-Process -Wait runas.exe #"
/machine:amd64 /trustlevel:0x20000 "powershell -c & \"$PSScriptRoot\test2.ps1\" -drive C:\ *> \"$outFile\""
"#
# Now $outFile contains all output produced by the other PowerShell script.
Write-Verbose -Verbose "Output from the runas.exe-launched PowerShell script:"
Get-Content -LiteralPath $outFile
$outFile | Remove-Item # Clean up.
Non-Elevated PowerShell Script (test2.ps1):
param([string]$drive)
function IsAdmin{
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$Is64 = [Environment]::Is64BitOperatingSystem
if ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Output "Running with elevated privileges. (64-bit=$Is64)"
} else {
Write-Output "Running without elevated privileges. (64-bit=$Is64)"
}
}
function Setup-Test{
Write-Output "Testing Powershell with Parameter Drive=$drive"
git config --global user.name
cd bob
Write-Error "Error Line 1
Error Line 2"
Write-Error "Error Line 3"
$d = 3/0
Write-Output "Done Testing Powershell"
}
IsAdmin
Setup-Test

This can be solved with a named pipe.
Elevated PowerShell Script (test.ps1)
function IsAdmin{
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$Is64 = [Environment]::Is64BitOperatingSystem
if ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Output "Running with elevated privileges. (64-bit=$Is64)"
} else {
Write-Output "Running without elevated privileges. (64-bit=$Is64)"
}
}
IsAdmin
Write-Output "Running $PSScriptRoot\test.bat"
Start-Process -FilePath "$PSScriptRoot\test.bat" -ArgumentList "C:\" -NoNewWindow
$np = new-object System.IO.Pipes.NamedPipeClientStream('.','SAMPipe', [System.IO.Pipes.PipeDirection]::In,[System.IO.Pipes.PipeOptions]::None,[System.Security.Principal.TokenImpersonationLevel]::Impersonation)
$np.Connect()
$sr = new-object System.IO.StreamReader($np)
while ($l=$sr.ReadLine()) {
Write-Output $l
}
$sr.Close()
$np.Close()
BAT file in the middle to de-elevate (test.bat)
runas /machine:amd64 /trustlevel:0x20000 "powershell -command %~dp0test2.ps1 -drive %1 >dummy.txt"
Non-Elevated PowerShell Script (test2.ps1)
param([string]$drive)
function IsAdmin{
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$Is64 = [Environment]::Is64BitOperatingSystem
if ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Output "Running with elevated privileges. (64-bit=$Is64)"
} else {
Write-Output "Running without elevated privileges. (64-bit=$Is64)"
}
}
function Setup-Test{
Write-Output "Testing Powershell with Parameter Drive=$drive"
git config --global user.name
cd bob
Write-Error "Error Line 1
Error Line 2"
Write-Error "Error Line 3"
$d = 3/0
Write-Output "Done Testing Powershell"
}
$np = New-Object System.IO.Pipes.NamedPipeServerStream('SAMPipe',[System.IO.Pipes.PipeDirection]::Out)
$np.WaitForConnection()
$sw = New-Object System.IO.StreamWriter($np)
$sw.WriteLine('Begin Non-Elevated Process Pipe')
Invoke-Command -ScriptBlock {
try {
IsAdmin
Setup-Test
} catch {
Write-Error $_
}
} -ErrorVariable errVar -OutVariable out
foreach ($line in $out){
$sw.WriteLine($line)
}
foreach ($line in $errVar) {
$sw.WriteLine($line)
}
$sw.WriteLine('End Non-Elevated Process Pipe')
$sw.Close()
$np.Close()
Output
Running with elevated privileges. (64-bit=True)
Running C:\Users\bmarty\source\PowerShellTest\test.bat
C:\Users\bmarty\source\PowerShellTest>runas /machine:amd64 /trustlevel:0x20000 "powershell -command C:\Users\bmarty\source\PowerShellTest\test2.ps1 -drive C:\ >dummy.txt"
Begin Non-Elevated Process Pipe
Running without elevated privileges. (64-bit=True)
Testing Powershell with Parameter Drive=C:\
Ben Marty
Cannot find path 'C:\Users\bmarty\source\PowerShellTest\bob' because it does not exist.
Error Line 1
Error Line 2
Error Line 3
Attempted to divide by zero.
System.Management.Automation.RuntimeException: Attempted to divide by zero. ---> System.DivideByZeroException: Attempted to divide by zero.
--- End of inner exception stack trace ---
at System.Management.Automation.ExceptionHandlingOps.CheckActionPreference(FunctionContext funcContext, Exception exception)
at System.Management.Automation.Interpreter.ActionCallInstruction`2.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
Attempted to divide by zero.
Attempted to divide by zero.
End Non-Elevated Process Pipe
Done running
I don't understand why the output of git config only appears in the output if I include >dummy.txt in the BAT file.

Try working with ACLs instead. You can set that up on the parent directory so you don't even need to run the script in an elevated context.
Just set up a "gitclone" account that can write into the repository parent directory and then add the rest of the users as read+execute.
The rest will come automagically through inheritance.
Then run script as that "gitclone" user.

Related

Start-Process powershell cannot find specified

Edit5: Adam's code works unless there are spaces in the path. That solution is at Powershell Opening File Path with Spaces
Edit4: Simplified further with a test for the path. Same Error.
If ($args[0] -eq "admin")
{
$TheScriptPath = "C:\Users\root\Desktop\script.ps1"
Test-Path ($TheScriptPath)
Start-Process "powershell -noexit" $TheScriptPath
}
Else { Write-Host "OK" }
Output when I call "powershell .\script.ps1 admin" is:
True
Start-Process : This command cannot be run due to the error: The system cannot find the file specified.
At C:\Users\root\Desktop\script.ps1:11 char:2
Edit3: Nevermind. Previous solution stopped working. Script is:
if ($args[0] -eq "admin)
{
$TheScriptPath = $myInvocation.MyCommand.Definition
Start-Process powershell -Verb runAs -Workingdirectory $PSScriptroot $TheScriptPath
exit
}
Write-Host "Ok"
Error when I call "powershell .\script.ps1 admin" is:
Start-Process : This command cannot be run due to the error: The system cannot find the file specified.
At C:\Users\root\Desktop\script.ps1:11 char:2
It's not even working when I hard-code the script path now, even with "-Verb runAs" removed.
Edit2: This is solved, I just can't accept my own answer for two days. Hopefully I remember to do that in case someone else comes along with this question.
Edit1: My script now reads:
If ($args[0] -eq "go")
{
$ThePath = $myInvocation.MyCommand.Definition
Start-Process powershell -Verb runAs $ThePath
Exit
}
Write-Host "OK"
It fails with the error below. However, if I hard-code the script path and write the script as:
If ($args[0] -eq "go")
{
Start-Process powershell -Verb runAs C:\Users\root\Desktop\script.ps1
Exit
}
Write-Host "OK"
It succeeds. I've also tried ""$myInvocation.MyCommand.Definition"" to no avail.
Original:
I have a powershell script that, at least in Windows 7, elevated the user and then ran the rest of the script. In Windows 10, however, it's giving me:
Exception calling "start" with "1" argument(s): "The system cannot find hte file specified"
At C:\Users\root\desktop\script.ps1:15 char:2
If ($True)
{
# We are not running "as Administrator" - so relaunch as administrator
# Create a new process object that starts PowerShell
$newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell";
# Specify the current script path and name as a parameter
$newProcess.Arguments = $myInvocation.MyCommand.Definition;
# Indicate that the process should be elevated
$newProcess.Verb = "runas";
# Start the new process
[System.Diagnostics.Process]::Start($newProcess);
# Exit from the current, unelevated, process
exit
}
Write-Host "Ok"
The script exists at this path, as it actually attempting to invoke itself. I'm at a loss here.
I'm running Powershell v5.1.15063.1155 on Windows 10 (v10.0.15063 Build 15063). If I run the following:
$context = [Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
if (-not $context.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Start-Process powershell -Verb runAs -ArgumentList $myInvocation.MyCommand.Definition
exit
}
Get-Date
Sleep -Seconds 4
You can try this as a workaround as it works for me.
To your question, I'd think something is wrong with the the ProcessStartInfo object you created ($newProcess). I've seen that error when the executable name supplied as a parameter can't be found. For example, if I run the following:
$newProcess = new-object System.Diagnostics.ProcessStartInfo "cocain";
$newProcess.Arguments = $myInvocation.MyCommand.Definition;
$newProcess.Verb = "runas";
[System.Diagnostics.Process]::Start($newProcess);
I get the error you described:
You're sure Powershell is in your path (where.exe powershell)? I know its a reach.

PowerShell Script A calls Script B then Close Script A while Script B is still running

I currently have a simple script (A) which uses Invoke-Expression to call script B.
#Call Script
$command = '\\BoxA\PowerShellScripts$\PS_CopyUIAutomationOutput.ps1'
Try
{Invoke-Expression $command }
Catch
{
Write-host "Error: "$_
}
But when it does this it keep script A running. What I want to do is script A calls script B and then script A is closed, while script B is still running logging is done to an shared path for script B, so I don't need to capture any error or logging in script A.
Just use the exit keyword:
Try
{Invoke-Expression $command }
Catch
{
Write-host "Error: "$_
}
exit
EDIT:
If you want it to exit directly after running the command just place it after the invoke expression like so:
Try
{Invoke-Expression $command
exit}
Catch
{Write-host "Error: "$_
}
Use Stop-Process and an automatic variable $PID to release the session of the Script A and close the PS window.
$command = '\\BoxA\PowerShellScripts$\PS_CopyUIAutomationOutput.ps1'
Try
{
Invoke-Expression $command
}
Catch
{
Write-host "Error: "$_
}
Stop-Process -Id $PID
Just use Start-Process
Start-Process -FilePath PowerShell.exe -Argumentlist $Command
exit
this will start $command in a different process and continues to next line
regards,
Kvprasoon

Ignoring powershell script failure for a particular line

I have a powershell script that defines $ErrorActionPreference = "Stop"
But i also have a start-process call that target a process that returns a non-standard exit code on success (1 instead of 0).
As a result of this, the script is failing even when the start-process is ok.
I tried to append the -ErrorAction "Continue" parameter in the start-process call but it didn't solve the problem.
The problematic line looks like this:
$ErrorActionPreference = "Stop"
...
start-process "binary.exe" -Wait -ErrorAction "Continue"
if ($LastExitCode -ne 1)
{
echo "The executable failed to execute properly."
exit -1
}
...
How could I prevent start-process from making the whole script fail.
Start-Process doesn't update $LASTEXITCODE. Run Start-Process with the -PassThru parameter to get the process object, and evaluate that object's ExitCode property:
$ErrorActionPreference = "Stop"
...
$p = Start-Process "binary.exe" -Wait -PassThru
if ($p.ExitCode -ne 1) {
echo "The executable failed to execute properly."
exit -1
}

PowerShell script run from TaskScheduler yielding empty array of running VM's

I can run my powershell script as administrator in powershell, and it yields good list of running VM's. But when I run it in TaskScheduler with highest privileges, it's showing an empty list of running VM's. We have Server 2008 R2, PowerShell V3, and I downloaded the Hyper-V module for powershell recently. I created an account on the server with Administrators privileges, and Administrators have full control for all directories that the script is copying files from/to.
Also, when I run the script through powershell, I needed to run as administrator. When I run it with the powershell prompt this is what it looks like:
C:\windows\system32> powershell -NoProfile -noninteractive -ExecutionPolicy bypass -Command "& c:\Scripts\BackupVhdShell_2_param.ps1 -single_backup_file_to_loc 'E:\' -single_backup_file_from_loc 'S:\SQL-bak.vhd'"
So that works from powreshell to start/stop vm's and copy files.
In Task Scheduler, this is how I have it set up and it yields the empty list of running VM's:
Run with highest privileges is checked. I have my login credentials saved so it can wake up the server when I'm not here or if it's not up.
In The Program/script field: %SystemRoot%\SysWow64\WindowsPowerShell\v1.0\powershell.exe
In the Add Arguments field: -NoProfile -noninteractive -ExecutionPolicy bypass -Command "& c:\Scripts\BackupVhdShell_2_param.ps1 -single_backup_file_to_loc 'E:\' -single_backup_file_from_loc 'S:\SQL-bak.vhd'"
Any thoughts? I'm not sure if TaskManager isn't finding HyperV module? Or maybe I need Runas to get it to be administrator? I'm having trouble finding info on that. This link was similar but different: http://ss64.com/nt/runas.html Same thing as this: http://peter.hahndorf.eu/blog/
This is what the majority of the script looks like. Note that I have since added logging to the file and know that this line is coming up empty when the script is run through TaskScheduler: <[array]$vmNames = #(Get-VM -Running | %{$_.elementname})>
Again, it works fine through powershell.
The script:
param($single_backup_file_to_loc, $single_backup_file_from_loc)
function StopVMsInOrder ([array][String]$vmNames){
#this function will stop VM's in list, sequentially
Write-Host "Processing virtual machines in order"
foreach ($name in $vmNames) {
Write-Host "Analyzing $name"
Try {
#Write-Host "...Saving $name"
#Save-VM -VM $name -wait -Force
Write-Host "..shutdown $name" #name)"
Invoke-VMShutdown -VM $name -Force #$vm.name
} #try
Catch {
Write-Host "Failed to get virtual machine $name"
} #catch
}#foreach
} #function StopVMsInOrder
function StartVMsInOrder ([array][String]$vmNames){
#this function will start VM's in list, sequentially as opposed to all at once
Write-Host "Processing virtual machines in order"
foreach ($name in $vmNames) {
Write-Host "Analyzing $name"
Try {
Write-Host "..Starting $name"
Start-VM -VM $name -wait
} #try
Catch {
Write-Host "Failed to get virtual machine $name"
} #catch
}#foreach
} #function StartVMsInOrder
function CopyFileToFolder ([string]$Source,[string]$destination){
# get filename
...
}
#################start of script##############
import-module Hyperv
#get list of running vm's
[array]$vmNames = #(Get-VM -Running | %{$_.elementname})
Write-Host "To: $single_backup_file_to_loc"
Write-Host "From: $single_backup_file_from_loc"
#call function to stop vm's
StopVMsInOrder $vmNames
if($single_backup_file_to_loc -ne " ")
{
#someone passed in a parameter for one-off use of script
[array]$destFileArray = #($single_backup_file_to_loc)
[array]$sourceFileArray = #($single_backup_file_from_loc)
}else
{
Write-Host "To Loc not Defined as param"
#get set up for what need to backup vhd's
#where back it up to
}
$i=0
for ($i = $sourceFileArray.GetLowerBound(0); $i -le $sourceFileArray.GetUpperBound(0); $i++) {
$tempSource = $sourceFileArray[$i]
$tempDest = $destFileArray[$i]
CopyFileToFolder $tempSource $tempDest
Write-Host "i: $i"
}
Write-Host "Done with vhd backup"
#call function to start vm's
StartVMsInOrder $vmNames
Write-Host "Done with vm start"
I finally figured it out! I changed it so I was using the other version of powershell in TaskScheduler: %SystemRoot%\system32.... Now it's finding the VM's.

Powershell script running fine on Windows 8 but not on Windows 7

I am very new to powershell and I'm not sure what I did wrong. It is running fine on my Windows 8 PC but when I send it to someone else (he has Windows 7; created this for him), he gets a not allowed to run scripts error.
Tried with -ExecutionPolicy RemoteSigned but still no luck.
##################
<# CONFIG START #>
##################
#replace the path with your steam path. For example: C:\Program Files (x86)\Steam\Steam.exe
$steam_path = "C:\Program Files (x86)\Steam\steam.exe"
#You can change it to Ethernet 1 or Ethernet 2 or Ethernet 3 etc depending on which adapter you want to disable.
#If you have custom name for them (you can rename them from control panel), have to use that name.
$adapter_name = "Ethernet 1"
<#
What program to run.
1: Steam Dota 2
2: Steam CS
3: Steam CSS
4: Steam CSGO
5: Custom Program 1
6: Custom Program 2
7: Custom Program 3
#>
$game_id = "5"
<# Custom Program path and arguments#>
$cp1_path = "C:\Program Files (x86)\counter-strike source\css.exe"
$cp1_arg = " "
$cp2_path = ""
$cp2_arg = " "
$cp3_path = ""
$cp2_arg = " "
$delay = 20
################
<# CONFIG END #>
################
"Checking admin permissions..."
If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
{
"Administrator permissions required."
$arguments = '-ExecutionPolicy RemoteSigned -file "' + $myinvocation.mycommand.definition + '"'
# $arguments
Start-Process powershell -Verb runAs -ArgumentList $arguments
Break
}
"Exiting Steam..."
Start-Process -FilePath $steam_path -ArgumentList "-shutdown" -Wait:$true
Start-Sleep -s 2
"Disabling Network Adapter..."
Disable-NetAdapter -Name $adapter_name -Confirm:$false
Start-Sleep -s 5
"Starting Game..."
Switch($game_id)
{
1
{
Start-Process -filepath "steam://rungameid/570"
}
2
{
Start-Process -filepath "steam://rungameid/10"
}
3
{
Start-Process -filepath "steam://rungameid/240"
}
4
{
Start-Process -filepath "steam://rungameid/730"
}
5
{
Start-Process $cp1_path -ArgumentList $cp1_arg
}
6
{
Start-Process $cp2_path -ArgumentList $cp2_arg
}
7
{
Start-Process $cp3_path -ArgumentList $cp3_arg
}
}
Start-Sleep -s $delay
"Enabling Network Adapter..."
Enable-NetAdapter $adapter_name -Confirm:$false
exit
If you sent him the script, then RemoteSigned is doing it's job just fine. He got the script remotely (from you) and it is not signed, so it won't be executed.
Tell your friend to navigate to the ps1 script in Windows Explorer and right click, then choose "Unblock." He will then need to restart the PowerShell instance if it has failed to run the script already since this kind of information is cached by Windows.
The error indicates you didn't correctly set the execution policy. Firstly, you have run Powershell as an administrator. To do this, right click on the Powershell icon in the start menu and click on "Run as Administrator". Next you have to run the following command in Powershell:
Set-ExecutionPolicy RemoteSigned
You will be prompted to allow the change. Type "Y" and press return.
Finally, try running your script.