Creating a simple PowerShell based installer with elevated privileges - powershell

I am trying to make a script that performs a bunch of installation operations, including executing other files, such as .reg files to update the registry. I created a batch file to kick it off and have code in the powershell script to self elevate to run as admin. The problem is that once run as admin, the working path is C:\Windows\system32, after which the script cannot file to other (relative) files it needs to run.
Install.bat:
powershell InstallSteps.ps1
InstallSteps.ps1:
param([switch]$Elevated)
function Test-Admin {
$currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())
$currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
if ((Test-Admin) -eq $false) {
if ($elevated)
{
# tried to elevate, did not work, aborting
}
else {
Start-Process powershell.exe -Verb RunAs -ArgumentList ('-noprofile -noexit -file "{0}" -elevated' -f ($myinvocation.MyCommand.Definition))
}
exit
}
# THIS SHOWS THE PATH IS "C:\WINDOWS\SYSTEM32"
Write-Host $pwd
# THIS FAILS, BECAUSE IT CAN'T FIND THE FILE
reg IMPORT EnableAllTrustedApps.reg
I have not found a way to pass in the relative path to these to make into the working path. It seems the "Start-Process" method of elevating loses all context of where the script was originally located.

I recommend always using this function to call external things. It's more reliable than relative paths.
function Get-Script-Directory
{
$scriptInvocation = (Get-Variable MyInvocation -Scope 1).Value
return Split-Path $scriptInvocation.MyCommand.Path
}
e.g.
$script_home = Get-Script-Directory
reg.exe IMPORT "$script_home\EnableAllTrustedApps.reg"
Note - in 3.0 or above you can use this new built-in variable instead of the function.
$PSScriptRoot
You might also want to try specifying the -WorkingDirectory parameter for Start-Process e.g. Start-Process -FilePath powershell.exe -verb RunAs -WorkingDirectory $PWD.Path

Related

Powershell-Command to download a file with admin rights

I try do download a file via a powershell command. The command I use is simple:
Start-BitsTransfer -Source 'https://download.com/file.zip' -Destination 'E:\test\file.zip'
I can run the command in PS succesfully. But now I want to run it with elevated rights. So I gooogled and found this solution:
There it says the command should be:
Start-Process powershell.exe -Verb Runas -ArgumentList "-Command & {get-process}"
So I tried adjusting it for my use case:
Start-Process powershell.exe -Verb Runas -ArgumentList "-Command & {Start-BitsTransfer -Source 'https://download.com/file.zip' -Destination 'E:\test\file.zip'}"
But all is does is open a new PS-Window and closing it right after. Where is my mistake?
You can change to this
Start-Process powershell.exe -Verb Runas -ArgumentList "& {Start-BitsTransfer -Source 'https://download.com/file.zip' -Destination 'E:\test\file.zip'}"
Note the window will close after the execution completes. If you would like to see the output/errors (such as what would be shown in your non working example) just add another command to pause.
Start-Process powershell.exe -Verb Runas -ArgumentList "& {Start-BitsTransfer -Source 'https://download.com/file.zip' -Destination 'E:\test\file.zip';pause}"
& is used to invoke a command. It's useful for executing strings or scriptblocks. It runs in a child runspace.
& 'Get-Host'
& 'Write-Host' Hello -Fore Green
& {Write-Host Goodbye -Fore Cyan}
; is used to separate different commands on the same line.
& {$name = 'Doug';Write-Host Hello $name}
You can also use a period to invoke a scriptblock in the current runspace. In the previous command the $name variable would be empty in the callers scope where the following command would leave the variable defined.
& {$name = 'Doug';Write-Host Hello $name}
$name # empty as it all happens in the child scope
vs
. {$name = 'Doug';Write-Host Hello $name}
$name # populated because it's brought into the caller's scope

How to call powershellscript file by passing path parameters from batch file with administrator privileges?

I know this has many answers on internet. I tried many of the sample codes but didn't work for me.
My requirement:
I have a powershell file CreateLink.ps1 this creates a link for the source folder($source) in destination folder($destination) with given link name($link). All three values are passed as arguments to this file. If I execute these with Powershell CLI as
C:\codedeploy\UAT\CreateLink.ps1 -source C:\Deployments\UAT\ -destination C:\codedeploy\UAT\release -link publish
soft link name publish of destinatio path C:\codedeploy\UAT\release is created in source path works fine. Below is CreateLink.ps1 code:
param([parameter(mandatory=$true)]
$source,
$destination,
$link)
# Write-Host $source
# Write-Host $destination
if ($PSHOME -like "*SysWOW64*")
{
Write-Warning "Restarting this script under 64-bit Windows PowerShell."
# Restart this script under 64-bit Windows PowerShell.
# (\SysNative\ redirects to \System32\ for 64-bit mode)
& (Join-Path ($PSHOME -replace "SysWOW64", "SysNative") powershell.exe) -File `
(Join-Path $PSScriptRoot $MyInvocation.MyCommand) #args
# Exit 32-bit script.
Exit $LastExitCode
}
# Release Root path
$release_root="$source"
# Deployment Root path
$deployment_root="$destination"
$symlink="$link"
# Make a Symlink path
$pathLink="$release_root\$symlink"
# Delete Symlink if exists and create anew
if((Test-Path $pathLink) -ne 0)
{
Write-Output "Delete release Link: $pathLink<--"
# Powershell issue w/ Symlinks
# https://github.com/PowerShell/PowerShell/issues/621
# Fix: https://kristofmattei.be/2012/12/15/powershell-remove-item-and-symbolic-links/
# rm -r $pathLink -Force
$RMDIR = "/c rmdir $pathLink"
Start-Process cmd.exe -ArgumentList $RMDIR -Wait
}
$MKLINK = "/c mklink /D $pathLink $deployment_root"
Start-Process cmd.exe -ArgumentList $MKLINK -Wait
Now I need to call this powershell script file with arguments in a batch file after-deploy.bat in jenkins job. I tried below code:
Powershell.exe -executionpolicy remotesigned -File C:\codedeploy\UAT\CreateLink.ps1 -source C:\Deployments\UAT\ -destination C:\codedeploy\UAT\release -link publish
and
powershell -Command "&{ Start-Process powershell -ArgumentList '-File C:\codedeploy\UAT\CreateLink.ps1' -source C:\Deployments\UAT\ -destination C:\codedeploy\UAT\release -link publish -Verb RunAs}"
few other commands also didn't work for me. Any help on how to run this command with parameters from batch file would be appreciated. If possible please provide snippet of code.
Thanks in advance.

Trying to run commands remote with powershell but not luck

I'm looking for help to run commands on remote computers regarding a line with mcafee agent to get it command run remotely.
$Machines = Get-Content -Path "C:\server_list.txt"
foreach ($computer in $Machines){
Write-host "Executing Events on $computer" -b "yellow" -foregroundcolor "red"
$command = Start-Process -NoNewWindow -FilePath "C:\Program Files\McAfee\Agent\cmdagent.exe" -ArgumentList "/e /l C:\temp"
Invoke-Command -ComputerName $computer -ScriptBlock {$command}
}
When I executed this command run locally but not remotely.
I'm looking for help here I have not full experience but I'm started automating some task on my job.
Please suggest some tips
I really appreciate it
Thanks
$command = Start-Process -NoNewWindow -FilePath "C:\Program Files\McAfee\Agent\cmdagent.exe" -ArgumentList "/e /l C:\temp"
This doesn't define a command for later execution with Invoke-Command, it instantly executes the Start-Process command, which is not your intent, and it is the reason it runs locally.
To fix that, you'd have to define it as a script block ({ ... }):
$command = { Start-Proces ... }, and then pass it as-is to Invoke-Command's -ScriptBlock parameter (Invoke-Command -ComputerName $computer -ScriptBlock $command) (don't enclose it in { ... } again).
Additionally, I suggest taking advantage of Invoke-Command's ability to target multiple computers at once, in parallel, and to avoid using Start-Process for synchronous invocation of an external program in the same window.
To put it all together:
$machines = Get-Content -Path "C:\server_list.txt"
Write-host "Executing Events on the following computers: $machines" -b "yellow" -foregroundcolor "red"
# Define the command as a script block, which is a piece of PowerShell code
# you can execute on demand later.
# In it, execute cmdagent.exe *directly*, not via Start-Process.
$command = { & "C:\Program Files\McAfee\Agent\cmdagent.exe" /e /l C:\temp }
# Invoke the script block on *all* computers in parallel, with a single
# Invoke-Command call.
Invoke-Command -ComputerName $machines -ScriptBlock $command
Note the need to use &, the call operator, to invoke the cmdagent.exe executable, because its path is quoted (of necessity, due to containing spaces).
Alternatively, you can define the script block directly in the Invoke-Command call:
Invoke-Command -ComputerName $machines -ScriptBlock {
& "C:\Program Files\McAfee\Agent\cmdagent.exe" /e /l C:\temp
}
A notable pitfall when targeting remote computers is that you cannot directly reference local variables in the (remotely executing) script block, and must instead explicitly refer to them via the $using: scope; e.g., $using:someLocalVar instead of $someLocalVar - see this answer for more information.
The problem is that $command is only valid for your local session - if you try to use it in a remote session, $command is $null and does nothing. Additionally, you are actually assigning $command whatever Start-Process returns, not what you want the command to be.
Just put the command in the script block (and while you're at it you can run this on every machine with a single command without having to loop over each one synchronously):
Invoke-Command -ComputerName $Machines -ScriptBlock { Start-Process -NoNewWindow -FilePath "C:\Program Files\McAfee\Agent\cmdagent.exe" -ArgumentList "/e /l C:\temp" }

Relaunching PowerShell script as admin user

I have a quite a few computer systems which we need to deploy software. I've been using a simple method for detecting if a user is a local admin, then detecting if they have admin rights. If needed, the script relaunches with elevated privileges. If the user is not a local admin, the script relaunches using a different credentials (local admin). The script works great on systems which have a later version of PowerShell such as Windows 8 and Windows 10.
The problem is when the user is not an admin and the script is running on Windows 7. The script uses $PSScriptPath to relaunch the script. I don't think this works in earlier versions of PowerShell. So I tried setting $PSScriptRoot myself if the Major PowerShell version is < 3. The problem is then the script gets stuck in a loop of some sort where it just constantly opens and closes windows and then I have to kill it... If I don't define $PSScriptRoot I get the error
Cannot bind argument to parameter 'Path' because it is null
I assume this is because $PSScriptRoot isn't defined in PowerShell 2.0.
Here's an example of what I'm trying to do:
#Check if PowerShell version is greater than 2. If not, set $PSSriptRoot.
if ($PSVersionTable.PSVersion.Major -lt 3) {
$PSScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Definition
}
#Check if the user is a local admin. If they are, set $LocalAdmin to $True.
$LocalAdmin = $false
if ((net localgroup administrators) -match ([System.Environment]::UserDomainName + "\\" + [System.Environment]::Username)) {
$LocalAdmin = $true
}
if ($LocalAdmin) {
#Check if the local admin needs to run the script as administrator
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
$arguments = "& '" + $MyInvocation.MyCommand.Definition + "'"
Start-Process powershell -Verb runas -ArgumentList $arguments
break
}
} else {
#Not a local admin. Relaunch script as admin user.
Start-Process -Credential $credential (Join-Path $PSHome powershell.exe) -ArgumentList (#("-File",
(Join-Path $PSScriptRoot $MyInvocation.MyCommand)) + $args)
exit
}
Don't re-define automatic variables. Nothing good will come of it.
Besides, why do you want to anyway? The only thing you use $PSScriptRoot for is to reconstruct the script path you already have. Just assign that path to a variable and use that in your script.
$script = $MyInvocation.MyCommand.Definition
$ps = Join-Path $PSHome 'powershell.exe'
$isLocalAdmin = [bool]((net localgroup administrators) -match "$env:USERDOMAIN\\$env:USERNAME")
if ($isLocalAdmin) {
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]'Administrator')) {
Start-Process $ps -Verb runas -ArgumentList "& '$script'"
exit
}
} else {
Start-Process $ps -ArgumentList (#('-File', $script) + $args) -Credential $credential
exit
}

Powershell running as a another user with elevated privileges

I have two scripts located in C:\setup: script.ps1 and script1.ps1.
I want to be able to run the script1.ps1 from withing script.ps1 as another user and with elevated privileges but I cannot make it work. The new powershell window opens but closes immediately ...
here is the script:
$cspath = $MyInvocation.MyCommand.Path
$sfolder = Split-Path $cspath
$spath = Join-Path $sfolder "\Script1.ps1"
$sa = "domain\user"
$sap = "userpassword"
$sasp = ConvertTo-SecureString -String $sap -AsPlainText -Force
$sac = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $sa, $sasp
Start-Process $PSHOME\powershell.exe `
-Credential $sac `
-ArgumentList "-Command Start-Process $PSHOME\powershell.exe -ArgumentList `"'$spath'`" -Verb Runas" -Wait
Any help will be appreciated ...
It looks like you might need to adjust your parameters for powershell.exe. Instead of using -ArgumentList, which I don't think is valid, you should use the -File parameter. Also, you will want to use the -ExecutionPolicy Bypass parameter to ensure that the script execution policy is not interfering.
Finally, I would recommend removing the single quotes from around the script path, as the Windows command interpreter does not understand single quotes to surround parameters.
Give this a try:
$ArgumentList = '-Command Start-Process -FilePath $PSHOME\powershell.exe -ArgumentList "-ExecutionPolicy Bypass -File \"{0}\"" -Verb Runas' -f $sPath;
Start-Process $PSHOME\powershell.exe `
-Credential $sac `
-ArgumentList $ArgumentList -Wait
Update
It seems that some quoting rules were at play here as well, since we are embedding one command inside of another. I wrote and tested a fully function script on PowerShell v4.0.
Here are the contents:
# Create test directory and script file
[void](New-Item -Path c:\test -ItemType Directory -Force);
Set-Content -Path c:\test\test1.ps1 -Value 'Add-Content -Path $PSScriptRoot\blah.txt -Value (Get-Date);';
# Get credential and define script path
$Credential = Get-Credential;
$ScriptPath = 'c:\test\test1.ps1';
# Define the command line arguments
$ArgumentList = 'Start-Process -FilePath powershell.exe -ArgumentList \"-ExecutionPolicy Bypass -File "{0}"\" -Verb Runas' -f $ScriptPath;
Start-Process -FilePath powershell.exe `
-Credential $Credential `
-ArgumentList $ArgumentList -Wait -NoNewWindow;
I can confirm that I get a UAC prompt, and the target script successfully executes.
Since you're concerned about the new session window closing, I'm guessing you want command line output.
Start-Process is working as intended. It will run the script passed in through -ArgumentList and exit the session. This means it will not hold to display command line output - the session will terminate immediately after the process completes.
If you want a persistent session, use New-PSSession. Otherwise, you could export the data you're gathering to a file.