Restart script as admin and preserve switch and string parameters - powershell

I'm writing a PowerShell script that uses parameters / arguments and needs to be run as administrator but I'm trying to make it as user-friendly as possible so I'm trying to write it so that if it wasn't run as administrator then it can auto-elevate itself and preserve the original parameters (only switches and strings).
I was not able to find a solution online, hence this post.

I managed to accomplish this by "stringifying" $PsBoundParameters then using that with Start-Process PowerShell -Verb Runas -ArgumentList.
Note: $PsBoundParameters uses the parameters of the current scope ("root" vs inside a function, for example) so if you need to reference the command-line parameters (as I do) then you'll need to either use this variable outside of a function or first pass the variable to the function (as I've done here).
I've created a demonstration of this:
Param(
[switch]$ExampleSwitch,
[string]$ExampleString
)
Function Restart ($AllParameters, $Admin) {
$AllParameters_String = "";
ForEach ($Parameter in $AllParameters.GetEnumerator()){
$Parameter_Key = $Parameter.Key;
$Parameter_Value = $Parameter.Value;
$Parameter_Value_Type = $Parameter_Value.GetType().Name;
If ($Parameter_Value_Type -Eq "SwitchParameter"){
$AllParameters_String += " -$Parameter_Key";
} Else {
$AllParameters_String += " -$Parameter_Key $Parameter_Value";
}
}
$Arguments = "-File `"" + $PSCommandPath + "`" -NoExit" + $AllParameters_String;
If ($Admin -Eq $True){
Start-Process PowerShell -Verb Runas -ArgumentList $Arguments;
} Else {
Start-Process PowerShell -ArgumentList $Arguments;
}
}
$RanAsAdministrator = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator);
Write-Host "ExampleSwitch value:" $ExampleSwitch;
Write-Host "ExampleString value:" $ExampleString;
Write-Host "";
If ($RanAsAdministrator -Eq $True){
Write-Host "Running as administrator: Yes.";
} Else {
Write-Host "Running as administrator: No.";
}
$Elevate = Read-Host "Restart as current user or admin? (u/a)";
Write-Host "";
If ($Elevate -Like "u"){
Restart $PsBoundParameters;
} ElseIf ($Elevate -Like "a") {
Restart $PsBoundParameters -Admin $True;
}
Start-Sleep -Seconds 9999;

Param(
[Switch]$MySwitch,
[String]$MyString,
[Switch]$NoExit
)
If (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
$PSHost = If ($PSVersionTable.PSVersion.Major -le 5) {'PowerShell'} Else {'PwSh'}
Start-Process -Verb RunAs $PSHost (#(' -NoExit')[!$NoExit] + " -File `"$PSCommandPath`" " + ($MyInvocation.Line -split '\.ps1[\s\''\"]\s*', 2)[-1])
Break
}
Write-Host "MySwitch:" $MySwitch;
Write-Host "MyString:" $MyString;
Explanation:
($MyInvocation.Line -split '\.ps1[\s\''\"]\s*', 2)[-1]) will resolve the current parameters
$MyHost determines the current PowerShell Host (PowerShell for Windows or PowerShell Core) and use the same host for the elevated window.
The -NoExit switch will prevent the elevated window to automatically close
Example:
.\RunAsAdministrator.ps1 -MyString "Test 123" -MySwitch -NoExit

Related

Elevating Powershell to admin and keeping passed arguments

This was an unsuccessful attempt by me to explain the issue, please scroll down to see a hopefully better explanation
So I'm trying to have my script self-elevate itself to use admin rights.
I think I tried every fix possible on how to elevate a PS session to admin rights but it seems that none can make my arguments stick after the PS session re-opens. I'm assuming it's me who misunderstands how to do this properly.
I have a script that has only one parameter that can be passed to it. Here is the relevant block of code that includes the parameter in the script and the function that I call to elevate to admin permissions:
param (
[Parameter(Mandatory=$false)][bool]$Param1= $false
)
function openWithPriv {
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Start-Process PowerShell -Verb RunAs "-NoProfile -ExecutionPolicy Bypass -Command `"cd '$pwd'; & '$PSCommandPath';`";`"$args`"";
exit;
}
}
When I run my script and add my parameter, it just skips that function all together. For example: .\script.ps1 -Param1 $true runs the script in its' entirety but when it reaches my function, it just goes to the Default switch:
function runParam1 {
[CmdletBinding()]
param (
[Parameter(Mandatory=$false)][bool]$Param1= $false
)
switch ($Param1) {
$true {
Write-Host "script works"
}
Default {
Write-Host "script didn't run" -ForegroundColor Red
}
}
}
By the way, here is how I call all of the functions in my script, maybe I'm doing something wrong here as well?
#Run all functions
try {
openWithPriv
runParam1 -Param1 $Param1
someFunction1
someFunction2
}
catch {
Write-Host "Unknown error" -ForegroundColor Red -ErrorAction SilentlyContinue
Read-Host -Prompt "Press any key to continue"
}
What am I missing? Any help to fix this would be great :)
This is another attempt to explain, with the full script
So here is my script:
param (
[Parameter(Mandatory=$false)][bool]$param1 = $false
)
function openWithPriv {
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Start-Process PowerShell -Verb RunAs "-NoProfile -ExecutionPolicy Bypass -Command `"cd '$($PWD.Path)'; & '$PSCommandPath';`";`"$args`"";
exit;
}
}
function func1 {
try {
Write-Host "executing function number 1" -BackgroundColor Blue
}
catch {
Write-Host "Unknown error in func1" -ForegroundColor Red -ErrorAction SilentlyContinue
}
}
function func2 {
[CmdletBinding()]
param (
[Parameter(Mandatory=$false)][bool]$param1 = $false
)
switch ($param1) {
$true {
Write-Host "Executing function 2 because param1 was passed"
}
Default {
Write-Host "Skipping func2 because param1 is false" -ForegroundColor Yellow
}
}
}
function func3{
try {
Write-Host "Executing function 3"
}
catch {
Write-Host "Error, couldn't execute func3" -ForegroundColor Red -ErrorAction SilentlyContinue
}
}
#Run all functions
try {
openWithPriv
func1
func2 -param1 $param1
func3
Read-Host "Script finishd without an issue, press any key to exit"
}
catch {
Write-Host "Unknown error somewhere in the script" -ForegroundColor Red -ErrorAction SilentlyContinue
Read-Host -Prompt "Press any key to continue"
}
The issue:
When I execute the script and give it the parameter, nothing happens, it just skips that function and goes to the default switch which is to just prompt the user that the function was skipped.
Example
When I run .\test.ps1, this is the output:
When I run .\test.ps1 -param1 $true
This is the output:
The output should include this text Executing function 2 because param1 was passed as it's shown in func2.
Bottom line
As you can see, because I elevate the script to use admin rights the parameter I'm passing is "lost" when it reaches that function.
Hope this was a bit more clear :)
OK I think you loos the param1 when you start an other process. The $Args are not present and not passed to the script. I splited the code a little to make it clearer
Start-Process PowerShell -Verb RunAs "
-NoProfile -ExecutionPolicy Bypass -Command `"
cd '$pwd';
& '$PSCommandPath';
`";`" # this is just the string ";"
$args # these $args are written after the script is executed
`"
";
Instead you need to pass the $param1 to the script:
Start-Process PowerShell -Verb RunAs "
-NoProfile -ExecutionPolicy Bypass -Command `"
cd '$pwd';
& '$PSCommandPath' -param1 $param1
`"
";
However since $param1 is [bool] and will only accept [bool] you get an error because you are in a string and $param1 will automically be cast to [string] = True instead of $true. To prevent this use 1 and 0 instead:
if ($param1){
$Arg = 1
}else{
$Arg = 0
}
...
Start-Process PowerShell -Verb RunAs "-NoProfile -Noexit -ExecutionPolicy Bypass -Command `"cd '$($PWD.Path)'; & '$PSCommandPath' -param1 $Arg`"";
This could be shortened to:
$Arg = [int]$param1

How to get the return value of an application in PowerShell?

I am passing an argument to my test application via powershell. I wish to get the return value of the application once it finishes.
How can I get the return value of the application instead of the console output.
I'm running the application by running
Test.ps1 2
Test.ps1
param ([string]$param1)
$path = "C:\Workspaces\myapplication\"
$executable = "Test.exe"
$filepath = "$($path)$($executable) $($param1)"
Try
{
$Result = iex $filepath
#this writes out the console output of Test.exe instead of the return value.
Write-Host $Result
Write-Host $LASTEXITCODE
}
Catch
{
Write-Host "Exit Code"
Write-Host $LASTEXITCODE
}
you should use Start-Process:
$p = Start-Process $($path)$($executable) -ArgumentList $input
$p.HasExited
$p.ExitCode

Powershell Running as Administrator not working?

I need some way to be able to run the below script as Administrator.
Script to get the Security Event log:
$DateAfter = (Get-Date).AddDays(-1)
$DateBefore = (Get-Date)
$EventLogTest = Get-EventLog -LogName Security -InstanceId 4625 -Before $DateBefore -After $DateAfter -Newest 5
$WinEventTest = Get-WinEvent -FilterHashtable #{ LogName = 'Security'; Id = 4625; StartTime = $DateAfter; EndTime = $DateBefore } -MaxEvents 5
Write-Host "$EventLogTest result is: "
$EventLogTest
Write-Host "$WinEventTest result is: "
$WinEventTest
I have compiled the below snippets, but somehow, the result is not displayed or nothing?
Combined Script:
$Role = "Domain Admins"
$CurrentLoginPrincipal = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent())
$IsDomainAdminGroupMember = $CurrentLoginPrincipal.IsInRole($Role)
$IsLocalComputerAdminMember = $CurrentLoginPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
If( -not ($IsDomainAdminGroupMember -and $IsLocalComputerAdminMember) ) {
Write-Warning "You are not running this as $($Role) and Local Administrator of $($ENV:COMPUTERNAME).$($ENV:USERDNSDOMAIN). The script will be re-executed as Local Administrator"
Try {
Start-Process PowerShell -Verb RunAs "-NoProfile -ExecutionPolicy Bypass -Command `"cd '$pwd'; & '$PSCommandPath';`"" -Verbose
}
Catch {
Write-Warning -Message "[PROCESS] Something wrong happened"
Write-Warning -Message $Error[0].Exception.Message
$out.Details = $_.Exception.Message
Write-Host " ERROR: $($out.Details)" -ForegroundColor Red
}
}
Else {
#a user running the script has the Domain Admins and Local PC Admin rights
Write-Host " $($CurrentLoginPrincipal.Identity.Name.ToString()) is currently member of $($Role) and Local Administrator of $($ENV:COMPUTERNAME).$($ENV:USERDNSDOMAIN) " -ForegroundColor Green
}
$DateAfter = (Get-Date).AddDays(-1)
$DateBefore = (Get-Date)
$EventLogTest = Get-EventLog -LogName Security -InstanceId 4625 -Before $DateBefore -After $DateAfter -Newest 5
$WinEventTest = Get-WinEvent -FilterHashtable #{ LogName = 'Security'; Id = 4625; StartTime = $DateAfter; EndTime = $DateBefore } -MaxEvents 5
Write-Host "$EventLogTest result is: "
$EventLogTest
Write-Host "$WinEventTest result is: "
$WinEventTest
However, it is still not executing as Administrator to get the result displayed. How can I fix this?
First thing I noticed is that your if condition is wrong. It uses -and where that should be or (because either a Domain admin OR a local Administrator can run this)
Next, the arguments for Start-Process are incorrect. Personally, I like using the -ArgumentList as array.
Finally, in the catch block you use an undefined variable $out with an equally undefined property $out.Details. In the code below I have changed that to simply re-throw the exception.
Starting from where the if..else is:
if( -not ($IsDomainAdminGroupMember -or $IsLocalComputerAdminMember) ) {
Write-Warning "You are not running this as $($Role) or Local Administrator of $($ENV:COMPUTERNAME).$($ENV:USERDNSDOMAIN). The script will be re-executed as Local Administrator"
# give the user some time to see this message
Start-Sleep 4
# Build base arguments for powershell.exe as string array
$argList = '-NoLogo', '-NoProfile', '-NoExit', '-ExecutionPolicy Bypass', '-File', ('"{0}"' -f $PSCommandPath)
# Add script arguments if any
$argList += $MyInvocation.BoundParameters.GetEnumerator() | ForEach-Object {"-$($_.Key)", "$($_.Value)"}
try {
Start-Process PowerShell.exe -Verb Runas -WorkingDirectory $pwd -ArgumentList $argList -Verbose -ErrorAction Stop
# exit the current script.
exit # Use return if you want to keep this instance open aswell
}
catch {
throw
}
}
else {
#a user running the script has the Domain Admins and Local PC Admin rights
Write-Host " $($CurrentLoginPrincipal.Identity.Name.ToString()) is currently member of $($Role) and Local Administrator of $($ENV:COMPUTERNAME).$($ENV:USERDNSDOMAIN) " -ForegroundColor Green
}

PowerShell script executing batch script on remote server

I am executing a PowerShell script which executes a batch script at remote server. But in PowerShell script I am not able to handle any failure that could occur in batch script. The batch script is having exit %ERROR_CODE% at the end.
Please let me know how I can catch any error occurred in batch script in the calling PowerShell script.
My PowerShell script is like:
$DBServer = $args[0]
$CustName = $args[1]
$FullBackupPath = $args[2]
$command = "cmd.exe /c DbBackupBatch.cmd " + $FullBackupPath + " " + $CustName
$script = 'Invoke-Expression -Command "' + $command + '"'
$scriptblock = [scriptblock]::Create($script)
try {
Invoke-Command -ComputerName $DBServer -Authentication NegotiateWithImplicitCredential -ErrorAction Stop -ScriptBlock $scriptblock
exit 0
} catch {
$message = $_.Exception.Message
Write-Host $_.Exception.Message
# While executing a Java programs, we get message as below -
# Picked up JAVA_TOOL_OPTIONS: -Xms512m -Xmx512m
# This message is treated as error message by PowerShell, though it is not an error
if (($message.Length -lt 50) -and ($message.Contains('Picked up JAVA_TOOL_OPTIONS:'))) {
exit 0
} else {
Write-Host $_.Exception.Message
exit 1
}
}
Give this a whirl:
$remoteReturnValue = Invoke-Command -ComputerName "DV1IMPSSDB01" -Authentication NegotiateWithImplicitCredential -ScriptBlock {
$cmd = Start-Process "cmd.exe" -Wait -PassThru -ArgumentList "/c timeout 5"
$cmdExitCode = $cmd.ExitCode
if ($cmdExitCode -eq 0) {
return "Success"
}
else {
return "Wuh-oh, we have had a problem... exit code: $cmdExitCode"
}
}
Write-Host $remoteReturnValue -ForegroundColor Magenta
Whatever you're trying to do in PowerShell, Invoke-Expression is practically always the wrong approach. PowerShell can execute batch files all by itself, so you can run DbBackupBatch.cmd directly, without Invoke-Expression and even without cmd /c.
Try something like this:
$DBServer = $args[0]
$CustName = $args[1]
$FullBackupPath = $args[2]
try {
Invoke-Command -ComputerName $DBServer -ScriptBlock {
$output = & DbBackupBatch.cmd $args[0] $args[1] 2>&1
if ($LastExitCode -ne 0) { throw $output }
} -ArgumentList $FullBackupPath, $CustName -Authentication NegotiateWithImplicitCredential
} catch {
Write-Host $_.Exception.Message
exit 1
}
exit 0

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