Check if given process is running with elevated right with powershell and Get-WmiObject - powershell

I have to following part of my script:
$active_processes = (Get-WmiObject -Class Win32_Process | where path -like $path | Select-Object -ExpandProperty Path | split-path -leaf | Select-Object -Unique)
It's working fine but I need to check if the process I get after all the script is running with elevated rights to launch another process with elevated rights if neccesary so it can interact with said process. I don't see any information about elevated rights with Get-WmiObject, I was wondering if I'm missing it or if there's another way to get that information
I don't need to run the powershell script as administrator. What I need is to find ff any executable requires elevated rights when launched and I need to find this information via powershell.

After some research on how windows knows if it needs admin to run an executable, I concluded that there are a couple ways but the most recommended and reliable is reading the executable manifest, so I wrote the following function:
function Get-ManifestFromExe{
Param(
[Parameter(Mandatory=$true,Position=0,ValueFromPipelineByPropertyName=$true)]
[Alias("Path")]
[ValidateScript({Test-Path $_ -IsValid})]
[String]$FullName
)
begin{
$stringStart = '<assembly'
$stringEnd = 'assembly>'
}
process{
$content = Get-Content $FullName -Raw
$indexStart = $content.IndexOf($stringStart)
$content = $content.Substring($indexStart)
$indexEnd = ($content.IndexOf($stringEnd)) + $stringEnd.Length
$content = $content.Substring(0,$indexEnd)
if($content -match "$stringStart(.|\s)+(?=$stringEnd)$stringEnd"){
return [XML]$Matches[0]
}
}
}
function Test-IsAdminRequired{
Param(
[Parameter(Mandatory=$true,Position=0)]
[XML]$xml
)
$value = $xml.assembly.trustInfo.security.requestedPrivileges.requestedExecutionLevel.level
if(-not [String]::IsNullOrEmpty($value)){
return ($value -eq "requireAdministrator" -or $value -eq "highestAvailable")
}else{
Write-Error "Provided xml does not contain requestedExecutionLevel node or level property"
}
}
$exe = '.\Firefox Installer.exe'
Get-ManifestFromExe -Path $exe
Test-IsAdminRequired -xml $exeManifest
It works by extracting the manifest XML from the executable and checking requestedExecutionLevel node's level property, the values accepted for this property are in this page, and quoted here:
asInvoker, requesting no additional permissions. This level requires
no additional trust prompts.
highestAvailable, requesting the highest permissions available to the
parent process.
requireAdministrator, requesting full administrator permissions.
So from this we can conclude that only highestAvailable and requireAdministrator would need admin privileges, so I check those, with that we will be done EXCEPT that some executables I tested (mostly installers) don't require admin to run but instead they prompt the UAC when they ruin their child executable, I don't really see a way to check this.. sorry.
BTW I really enjoyed this question (specially the research), hope this can help you.
SOURCES
What is the difference between "asInvoker" and "highestAvailable" execution levels?
reading an application's manifest file?
https://learn.microsoft.com/en-us/visualstudio/deployment/trustinfo-element-clickonce-application?view=vs-2019#requestedexecutionlevel

It's in the System.Security.Principal classes. This returns $true if the current user is elevated to local Administrator:
(New-Object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)

Related

How to prevent multiple instances of the same PowerShell 7 script?

Context
On a build server, a PowerShell 7 script script.ps1 will be started and will be running in the background in the remote computer.
What I want
A safenet to ensure that at most 1 instance of the script.ps1 script is running at once on the build server or remote computer, at all times.
What I tried:
I tried meddling with PowerShell 7 background jobs (by executing the script.ps1 as a job inside a wrapper script wrapper.ps1), however that didn't solve the problem as jobs do not carry over (and can't be accessed) in other PowerShell sessions.
What I tried looks like this:
# inside wrapper.ps1
$running_jobs = $(Get-Job -State Running) | Where-Object {$_.Name -eq "ImportantJob"}
if ($running_jobs.count -eq 0) {
Start-Job .\script.ps1 -Name "ImportantJob" -ArgumentList #($some_variables)
} else {
Write-Warning "Could not start new job; Existing job detected must be terminated beforehand."
}
To reiterate, the problem with that is that $running_jobs only returns the jobs running in the current session, so this code only limits one job per session, allowing for multiple instances to be ran if multiple sessions were mistakenly opened.
What I also tried:
I tried to look into Get-CimInstance:
$processes = Get-CimInstance -ClassName Win32_Process | Where-Object {$_.Name -eq "pwsh.exe"}
While this does return the current running PowerShell instances, these elements carry no information on the script that is being executed, as shown after I run:
foreach ($p in $processes) {
$p | Format-List *
}
I'm therefore lost and I feel like I'm missing something.
I appreciate any help or suggestions.
I like to define a config path in the $env:ProgramData location using a CompanyName\ProjectName scheme so I can put "per system" configuration.
You could use a similar scheme with a defined location to store a lock file created when the script run and deleted at the end of it (as suggested already within the comments).
Then, it is up to you to add additional checks if needed (What happen if the script exit prematurely while the lock is still present ?)
Example
# Define default path (Not user specific)
$ConfigLocation = "$Env:ProgramData\CompanyName\ProjectName"
# Create path if it does not exist
New-Item -ItemType Directory -Path $ConfigLocation -EA 0 | Out-Null
$LockFilePath = "$ConfigLocation\Instance.Lock"
$Locked = $null -ne (Get-Item -Path $LockFilePath -EA 0)
if ($Locked) {Exit}
# Lock
New-Item -Path $LockFilePath
# Do stuff
# Remove lock
Remove-Item -Path $LockFilePath
Alternatively, on Windows, you could also use a scheduled task without a schedule and with the setting "If the task is already running, then the following rule applies: Do not start a new instance". From there, instead of calling the original script, you call a proxy script that just launch the scheduled task.

Can PowerShell interact with RDP prompts?

I'm currently writing a script to automate a number of checks, I have a number of clients which I want to automatically log into one of their servers or use an app hosted via RDweb.
Right now my script works fine, however, I'm only able to get to the point that it'll start to execute the RDP pointer, I'm wondering if there's a way to hit "connect":
The method I'm currently using to run this:
[System.Diagnostics.Process]::Start("c:\file\path\file.rdp")
Is there a better way to run the .RDP file which will also allow you to "Connect"? I've also attempted to tick the "don't ask me" again, the next day it'll still prompt me with this message.
A solution I've found to start an RDP session that seems to work quite good is the following:
function Connect-RDP {
param (
[Parameter(Mandatory=$true)]
$ComputerName,
[System.Management.Automation.Credential()]
$Credential
)
# take each computername and process it individually
$ComputerName | ForEach-Object {
# if the user has submitted a credential, store it
# safely using cmdkey.exe for the given connection
if ($PSBoundParameters.ContainsKey('Credential'))
{
# extract username and password from credential
$User = $Credential.UserName
$Password = $Credential.GetNetworkCredential().Password
# save information using cmdkey.exe
cmdkey.exe /generic:$_ /user:$User /pass:$Password
}
# initiate the RDP connection
# connection will automatically use cached credentials
# if there are no cached credentials, you will have to log on
# manually, so on first use, make sure you use -Credential to submit
# logon credential
mstsc.exe /v $_ /f
}
}
Then you call it with Connect-rdp -ComputerName myserver -Credential (Get-Credential ).
Maybe you can adjust your script to use this cmdlet instead of your file.rdp.
I found the solution here:
https://www.powershellmagazine.com/2014/04/18/automatic-remote-desktop-connection/
Another way you could try is this:
[void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
# Get the ID of the process
$WindowsHandle = Get-Process | Where-Object { $_.MainWindowTitle -Match 'Remote Desktop Connection' } | Select-Object -ExpandProperty Id
# Activate the window
$wshell = New-Object -ComObject wscript.shell;
$wshell.AppActivate($WindowsHandle) | Out-Null
# SendKey to connect
[System.Windows.Forms.SendKeys]::SendWait("%{c}")
%{c} stands for ALT+C
The modifier keys are:
Key | Code
-----------
SHIFT +
CTRL ^
ALT %

Powershell Get-EventLog from computers.txt and save data

I have some problems getting EventLog and save data. I am able to get my EventLogs but not logs from network computers.
Here is the code I am running:
$logFileName = "Application"
$path = $MyInvocation.MyCommand.Path +"\Output\"
$path = $PSScriptRoot+"\Output\"
new-item $path -ItemType directory
$array = ("System", "Security")
$file = $PSScriptRoot +"\computers.txt"
$users = ForEach ($machine in $(Get-Content $file)) {
$pathMachine = $path+$machine
new-item $pathMachine -ItemType directory
ForEach ($logFileName in $array){
# do not edit
$logFileName
$exportFileName = (get-date -f yyyyMMdd) + "_" + $logFileName + ".evt"
$logFile = Get-WmiObject Win32_NTEventlogFile -ComputerName $machine | Where-Object {$_.logfilename -eq $logFileName}
$logFile
$exportFileName
$pathMachine
$temp = $pathMachine + "\"+ $exportFileName
$temp
$fff = $logFile.BackupEventLog($temp)
}
}
This could e considered a duplicate of this.
Reading event log remotely with Get-EventLog in Powershell
# swapped from this command
get-eventlog -LogName System -computername <ServerName>
# to this
invoke-command {get-eventlog -LogName System} -ComputerName <ServerName>
Don't struggle with writing this from scratch. Well, unless it's a learning exercise. There are pre-built script for you to leverage as is and or tweak as needed.
Running commands on Remote host require using the Invoke cmdlet, and or an established PSRemoting session to that host.
Get Remote Event Logs With Powershell
Gather the remote event log information for one or more systems using wmi, alternate credentials, and multiple runspaces. Function supports custom timeout parameters in case of wmi problems and returns Event Log information for the specified number of past hours.
Download: Get-RemoteEventLogs.ps1
The script is too long (it's 100+ lines) to post here, but here in the Synopsis of it.
Function Get-RemoteEventLogs
{
<#
.SYNOPSIS
Retrieves event logs via WMI in multiple runspaces.
.DESCRIPTION
Retrieves event logs via WMI and, if needed, alternate credentials. This function utilizes multiple runspaces.
.PARAMETER ComputerName
Specifies the target computer or comptuers for data query.
.PARAMETER Hours
Gather event logs from the last number of hourse specified here.
.PARAMETER ThrottleLimit
Specifies the maximum number of systems to inventory simultaneously
.PARAMETER Timeout
Specifies the maximum time in second command can run in background before terminating this thread.
.PARAMETER ShowProgress
Show progress bar information
.EXAMPLE
PS > (Get-RemoteEventLogs).EventLogs
Description
-----------
Lists all of the event logs found on the localhost in the last 24 hours.
.NOTES
Author: Zachary Loeber
Site: http://www.the-little-things.net/
Requires: Powershell 2.0
Version History
1.0.0 - 08/28/2013
- Initial release
#>
Or this one.
PowerShell To Get Event Log of local or Remote Computers in .csv file
This script is handy when you want to extract the eventlog from remote or local machine. It has multiple filters which will help to filter the data. You can filter by logname,event type, source etc. This also have facility to get the data based on date range. You can change th
Download : eventLogFromRemoteSystem.ps1
Again, too big to post here because the length is like the other one.
I am working on some assumptions but maybe this will help.
When I Ran your Code I got
Get-Content : Cannot find path 'C:\computers.txt' because it does not exist.
I had to make the C:\computers.txt file, then I ran your code again and got this error.
Get-Content : Cannot find path 'C:\Output\computers.txt' because it does not exist.
I made that file in that location, then I ran your code again and I got the event log file. Maybe try creating these two missing files with a command like
Get-WmiObject Win32_NTEventlogFile -ComputerName $machine
mkdir C:\Output\$machine
$env:computername | Out-File -FilePath c:\Output\Computers.txt
You may also want to setup a Network share and output to that location so you can access the event logs from a single computer. Once the share is setup and the permissions just drop the unc path in.

Negate having to Press Enter twice on PowerShell script

I am trying to run a PowerShell script daily through task scheduler, however the script will not run. When I enter the code below manually into PowerShell (as an administrator), it makes me press enter twice. I believe since i have to press enter twice is the reason it will not run through the task scheduler.
Is there a way to adjust my code to get this to work with the task scheduler?
I am running Windows 2012 R2 and Version 5.1 of PowerShell.
Please note that i ran the exact same script on my computer, which is Windows 10 and running version 5.1 of PowerShell, and it worked the correct way (only had to press enter once)
I expect to only press enter once to run my PowerShell script, but the actual output from the first time i press enter brings another line with just ">>" and then i press enter the second time and the script executes.
Powershell Script:
# Load WinSCP .NET assembly
Add-Type -Path "WinSCPnet.dll"
# Set up session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property #{
Protocol = [WinSCP.Protocol]::Sftp
HostName = ""
UserName = ""
Password = ""
SshHostKeyFingerprint = ""
}
$session = New-Object WinSCP.Session
try
{
# Connect
$session.Open($sessionOptions)
# Transfer files
$session.PutFiles("", "").Check()
}
finally
{
$session.Dispose()
}
If a script requires user interaction, then it really should not be a scheduled task.
If you write a script that requires confirmation, then you need to look using -Confirm parameter.
See Are you sure? Using the -WhatIf and -Confirm parameters in PowerShel
Remove-MailContact -Identity “$sourceEmail” -Confirm:$Y -WhatIf
The cmdlet or code you write has to support it. For code you write, that means using advanced functions.
How to write a PowerShell function to use Confirm, Verbose and WhatIf
function Set-FileContent
{
[cmdletbinding(SupportsShouldProcess)]
Param
(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$Content,
[Parameter(Mandatory = $true)]
[ValidateScript( {Test-Path $_ })]
[string]$File
)
if ($PSCmdlet.ShouldProcess("$File" , "Adding $Content to "))
{
Set-Content -Path $File -Value $Content
}
}
See also ConfirmPreference

Correctly set the current user as the new owner in PowerShell [duplicate]

How do I get the current username in Windows PowerShell?
I found it:
$env:UserName
There is also:
$env:UserDomain
$env:ComputerName
On Windows, you can:
[System.Security.Principal.WindowsIdentity]::GetCurrent().Name
I thought it would be valuable to summarize and compare the given answers.
If you want to access the environment variable:
(easier/shorter/memorable option)
[Environment]::UserName -- #ThomasBratt
$env:username -- #Eoin
whoami -- #galaktor
If you want to access the Windows access token:
(more dependable option)
[System.Security.Principal.WindowsIdentity]::GetCurrent().Name -- #MarkSeemann
If you want the name of the logged in user
(rather than the name of the user running the PowerShell instance)
$(Get-WMIObject -class Win32_ComputerSystem | select username).username -- #TwonOfAn on this other forum
Comparison
#Kevin Panko's comment on #Mark Seemann's answer deals with choosing one of the categories over the other:
[The Windows access token approach] is the most secure answer, because $env:USERNAME can be altered by the user, but this will not be fooled by doing that.
In short, the environment variable option is more succinct, and the Windows access token option is more dependable.
I've had to use #Mark Seemann's Windows access token approach in a PowerShell script that I was running from a C# application with impersonation.
The C# application is run with my user account, and it runs the PowerShell script as a service account. Because of a limitation of the way I'm running the PowerShell script from C#, the PowerShell instance uses my user account's environment variables, even though it is run as the service account user.
In this setup, the environment variable options return my account name, and the Windows access token option returns the service account name (which is what I wanted), and the logged in user option returns my account name.
Testing
Also, if you want to compare the options yourself, here is a script you can use to run a script as another user. You need to use the Get-Credential cmdlet to get a credential object, and then run this script with the script to run as another user as argument 1, and the credential object as argument 2.
Usage:
$cred = Get-Credential UserTo.RunAs
Run-AsUser.ps1 "whoami; pause" $cred
Run-AsUser.ps1 "[System.Security.Principal.WindowsIdentity]::GetCurrent().Name; pause" $cred
Contents of Run-AsUser.ps1 script:
param(
[Parameter(Mandatory=$true)]
[string]$script,
[Parameter(Mandatory=$true)]
[System.Management.Automation.PsCredential]$cred
)
Start-Process -Credential $cred -FilePath 'powershell.exe' -ArgumentList 'noprofile','-Command',"$script"
(you may need a hyphen before noprofile, like so)
Start-Process -Credential $cred -FilePath 'powershell.exe' -ArgumentList '-noprofile','-Command',"$script"
$env:username is the easiest way
I'd like to throw in the whoami command, which basically is a nice alias for doing %USERDOMAIN%\%USERNAME% as proposed in other answers.
Write-Host "current user:"
Write-Host $(whoami)
[Environment]::UserName returns just the user name. E.g. bob
[System.Security.Principal.WindowsIdentity]::GetCurrent().Name returns the user name, prefixed by its domain where appropriate. E.g. SOMEWHERENICE\bob
Now that PowerShell Core (aka v6) has been released, and people may want to write cross-platform scripts, many of the answers here will not work on anything other than Windows.
[Environment]::UserName appears to be the best way of getting the current username on all platforms supported by PowerShell Core if you don't want to add platform detection and special casing to your code.
I have used $env:username in the past, but a colleague pointed out it's an environment variable and can be changed by the user and therefore, if you really want to get the current user's username, you shouldn't trust it.
I'd upvote Mark Seemann's answer:
[System.Security.Principal.WindowsIdentity]::GetCurrent().Name
But I'm not allowed to. With Mark's answer, if you need just the username, you may have to parse it out since on my system, it returns hostname\username and on domain joined machines with domain accounts it will return domain\username.
I would not use whoami.exe since it's not present on all versions of Windows, and it's a call out to another binary and may give some security teams fits.
Just building on the work of others here:
[String] ${stUserDomain},[String] ${stUserAccount} = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name.split("\")
$username=( ( Get-WMIObject -class Win32_ComputerSystem | Select-Object -ExpandProperty username ) -split '\\' )[1]
$username
The second username is for display only purposes only if you copy and paste it.
I didn't see any Add-Type based examples. Here is one using the GetUserName directly from advapi32.dll.
$sig = #'
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool GetUserName(System.Text.StringBuilder sb, ref Int32 length);
'#
Add-Type -MemberDefinition $sig -Namespace Advapi32 -Name Util
$size = 64
$str = New-Object System.Text.StringBuilder -ArgumentList $size
[Advapi32.util]::GetUserName($str, [ref]$size) |Out-Null
$str.ToString()
Sometimes the Username attribute has no data in Win32_ComputerSystem even though there's a user signed in. What works for me is to use quser and parse the output. It's not perfect, but it works. E.g.:
$quserdata = #()
$quserdata = quser
$userid = ($quserdata[1] -split ' ')[1]
$userid
Note: if this is run as the user who is logged in, quser adds '>' symbol to the output. Then you need to get rid of that symbol, but mostly this is needed for code run as system or another account than the one that is logged in.
If you're used to batch, you can call
$user=$(cmd.exe /c echo %username%)
This basically steals the output from what you would get if you had a batch file with just "echo %username%".
I find easiest to use: cd $home\Desktop\
will take you to current user desktop
In my case, I needed to retrieve the username to enable the script to change the path, ie. c:\users\%username%. I needed to start the script by changing the path to the users desktop. I was able to do this, with help from above and elsewhere, by using the get-location applet.
You may have another, or even better way to do it, but this worked for me:
$Path = Get-Location
Set-Location $Path\Desktop
In my case, I needed to retrieve the username to enable the script to change the path, ie. c:\users\%username%\. I needed to start the script by changing the path to the users desktop. I was able to do this, with help from above and elsewhere, by using the get-location applet.
You may have another, or even better way to do it, but this worked for me:
$Path = Get-Location
Set-Location $Path\Desktop