How can I run powershell script that uses both local & elevated permissions? - powershell

I need to edit two registry keys. One needs to be run by the local user, the other by an account with elevated privileges.
How can I write a script that is run as a different user, but still has access to the local user's credentials?

As administrator, you can set the registry value for the currently logged on user too, if you can get the SID for that user.
When you have that, you can access the local users registry via the HKEY_USERS hive.
Import-Module ActiveDirectory
# set this to the registry path, property name and value you need to access
$regPath = 'Software\SomePath\SomeKey'
$propName = 'ThePropertyName'
$propValue = 'ThePropertyValue'
$propType = 'String' # use any of the `[Microsoft.Win32.RegistryValueKind]` enum values or names
# get the domain\username of the user currently logged in to the computer
$user = (Get-CimInstance -ClassName Win32_ComputerSystem).UserName
# get the SID for that user
$sid = (Get-ADUser -Identity ($user -split '\\', 2)[0]).SID
if (!$sid) {
throw "Could not determine the SID for user '$user'"
}
# Admin registry: HKEY_LOCAL_MACHINE
$path = Join-Path -Path 'HKLM:' -ChildPath $regPath
Set-Itemproperty -Path $path -Name $propName -Value $propValue -Type $propType
# Current user registry: HKEY_USERS
$path = Join-Path -Path "Registry::HKEY_USERS\$sid" -ChildPath $regPath
Set-Itemproperty -Path $path -Name $propName -Value $propValue -Type $propType
As mklement0 commented,
the above code uses the ActiveDirectory module to get the SID for the currently logged on user via Get-ADUser.
If this is not possible for you, or you are not in an AD domain, the following helper function can also get the SID, without the need for ActiveDirectory:
function Get-UserSID {
param (
[Parameter(ValuefromPipeline = $true, Position = 0)]
[Alias('Account', 'User')]
[string]$UserName = $env:USERNAME,
[string]$Domain = $env:USERDOMAIN
)
if ($UserName.Contains("\")) { $Domain, $UserName = $UserName -split '\\', 2 } #"# split on the backslash
try {
$objUser = New-Object System.Security.Principal.NTAccount($Domain, $UserName)
$strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier])
$strSID.Value
}
catch [System.Security.Principal.IdentityNotMappedException] {
Write-Warning "User '$UserName' does not exist in '$Domain'"
}
catch {
throw
}
}
Put that on top of your script and then use as:
# get the domain\username of the user currently logged in to the computer
$user = (Get-CimInstance -ClassName Win32_ComputerSystem).UserName
# get the SID for that user
$sid = Get-UserSID $user
A possible third way of getting the SID is by reading the registry:
# get the domain\username of the user currently logged in to the computer
$user = (Get-CimInstance -ClassName Win32_ComputerSystem).UserName
# get the SID for that user by probing the registry
$sid = ((Get-ItemProperty -Path 'Registry::HKEY_USERS\S-*\Volatile Environment' |
Where-Object { ('{0}\{1}' -f $_.USERDOMAIN, $_.USERNAME) -eq $user }).PSParentPath -split '\\')[-1]

You can use key "-credential" to set user that would be change registry like :
New-Item –Path "HKCU:\dummy" –Name newregistrykey -Credential myuser#domain
or to set property:
Set-Itemproperty -path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' -Name 'someProcess' -value 'C:\Program Files\someapp\myprogramm.exe' -Credential myuser#domain
And you can save you both cred to variables like:
$localCred = Get-Credential
$domainCred = Get-Credential

Related

Output membership of certain Window local groups

Working on a simple script to loop through a bunch of machines through a 3rd party system and output the machine, group, and the user to a PS object.
Have the script outputting the correct groups/users. However when a group has more than one user, then it renders it on the same line, instead of a new line. Just looking for insight on how to properly format the output so each result is on it's own line.
$params = $args
$Target = $args[0]
$PrivUser = "$($params[1])\$($params[2])"
$PrivPwd = ConvertTo-SecureString -String $params[3] -AsPlainText -Force
$cred = [pscredential]::new($PrivUser,$PrivPwd)
$Groups = #('Administrators','Power Users')
$results = #()
try {
Invoke-Command -ComputerName $Target -Credential $cred -HideComputerName -ScriptBlock {
$lgs = Get-LocalGroup -Name $Groups
Foreach ($lg in $lgs) {
$ms = Get-LocalGroupMember -Name $lg
#write-host $ms.Name
$output = New-Object PSObject -Property #{
Machine = $env:COMPUTERNAME
Group = "$lg"
Username=$ms
}
$results += $output
}
return $results
}
} catch {
throw "Unable to connect to target: $($args[0]) `n$_"
}
results:
Username Group Machine
-------- ----- -------
{BLT\clepley, BLT\clepley_admin, BLT\Domain Admins, BLT\svr.blt.div.ss...} Administrators BLT-SS-WEB
BLT\clepley_admin Power Users BLT-SS-WEB
Seems like you're missing an inner loop in case the membership is greater than one:
Invoke-Command -ComputerName $Target -Credential $cred -HideComputerName -ScriptBlock {
foreach($group in $using:Groups) {
foreach($member in Get-LocalGroupMember -Name $group) {
[pscustomobject]#{
Machine = $env:COMPUTERNAME
Group = $group
Member = $member.Name
ObjectClass = $member.ObjectClass
}
}
}
}
However note, there is no error handling here, hence, this assumes the Power Users Group exists in the remote computers.
It's also worth noting the use of the $using: scope modifier, which allows you to access the local variable $Groups in the remote scope.

Change value in registry on multiple servers using credentials

Looking to enable reg key on multiple remote machines.
Attempt 1:
$Servers = Get-Content "C:\PowerShell\TestServers.txt"
$Path = "HKLM:\SYSTEM\CurrentControlSet\Services\"
$Property = "*REG_WORD NAME*"
$Value = "1"
Foreach ($Server in $Servers)
{
Set-ItemProperty -Path $Path -Name $Property -Value $Value
}
Error: Set-ItemProperty : Requested registry access is not allowed.
NOTE: checked effective access, the account being used has FULLControl over the specific hive
Attempt 2:
Created a function, added the get-credential cmdlet
function Set-RemoteRegistryValue {
param (
$ComputerName,
$Path,
$Name,
$Value,
[ValidateNotNull()]
[System.Management.Automation.PSCredential]
[System.Management.Automation.Credential()]
$Credential = [System.Management.Automation.PSCredential]::Empty
)
$null = Invoke-Command -ComputerName $ComputerName -ScriptBlock {
Set-ItemProperty -Path $using:Path -Name $using:Name -Value $using:Value
} -Credential $Credential
}
I am now able to call the function and set the reg key value as desired, but only one machine at a time:
$remoteKeyParams = #{
ComputerName ='name'
Path = "HKLM:\SYSTEM\CurrentControlSet\Services\"
Name = "*keyname*"
Value = "1"
}
Set-RemoteRegistryValue #remoteKeyParams -Credential (Get-Credential)
I have tried putting multiple machines in as a string, and a text file:
[string]$ComputerName = "name","name","name"
ComputerName = c:\temp\testservers.txt
Am I doing something very wrong here?
Confirm you have one server per line and then this is how you should write it.
$Servers = Get-Content "C:\PowerShell\TestServers.txt"
$Path = "HKLM:\SYSTEM\CurrentControlSet\Services\"
$Property = "*REG_WORD NAME*"
$Value = "1"
Invoke-Command -ComputerName $servers -ScriptBlock {
Set-ItemProperty -Path $using:Path -Name $using:Name -Value $using:Value
} -Credential $Credential
When you pass all the server names to Invoke-Command it will run them all asynchronously (up to 32 by default on 5.1)

Powershell - New User home folder permissions

I am working on a PS script to automate new network accounts, their home folder and exchange mailbox. We have multiple Domain controllers so am looking for a way of creating a network account on one domain controller but creating the home directory on a different site with its own domain controller. I have tried this but when setting permissions an issue has occurred because the account has not replicated over to the other DC. Anyone have any ideas to get around this?
New Account Function
Function New-BVStandardUser
{
Param (
$FirstName,
$LastName,
$CallRef,
$SiteName,
$EmployeeID,
$ExpiryDate,
$InternetAccess,
$ExternalEmailAccess
)
$ImportGroups = Import-Csv -Path "\\UKSP-FS01\Lawsonja$\Scripts\New-ADUser\SiteGroups.csv" -Delimiter ","
$ImportServers = Import-Csv -Path "\\UKSP-FS01\Lawsonja$\Scripts\New-ADUser\SiteServers.csv" -Delimiter ","
$ImportOUs = Import-Csv -Path "\\UKSP-FS01\Lawsonja$\Scripts\New-ADUser\SiteOUs.csv" -Delimiter ","
# Convert the first and last name so it does not have special characters for the email address/ UPN
$LastNameEdit = $LastName -replace '[^a-zA-Z]', ''
$FirstNameEdit = $FirstName -replace '[^a-zA-Z]', ''
# Fetch a free username from AD based on the provided first and last name from the user
$Username = Get-ADUsername -FirstName $FirstNameEdit -LastName $LastNameEdit
# Generate a random password using the imported module
$Password = Get-Randompassword
# Create the AD account based on the inputted fields
$Params = #{
DisplayName = "$($LastName), $($FirstName)"
DirectoryName = "$($LastName), $($FirstName)"
SamAccountName = "$Username"
UserPrincipalName = "$FirstNameEdit.$LastNameEdit#Bakkavor.com"
Comment = "Created $($env:USERNAME) - $(Get-Date -Format dd/MM/yy) - $($CallRef)"
GivenName = "$FirstNameEdit"
Surname = "$LastNameEdit"
Description = "$($SiteName) User"
Enabled = $true
ChangePasswordAtLogon = $true
Path = "$ImportOUs.$($SiteName)"
HomeDirectory = "\\$ImportServers.$($SiteName)\$Username$"
HomeDrive = "U"
AccountPassword = (ConvertTo-SecureString $Password -AsPlainText -Force)
}
try
{
New-ADUser #Params -ErrorAction Stop
Write-Verbose -Verbose "Network Account Created"
}
catch
{
Write-Warning "Error creating network account. Error: $($_.Exception.Message)"
break
}
New Home Drive Function
Function New-BVUDrive
{
Param
(
$Username,
$Server
)
# Connect to the relevant server in CSV, create new folder, create new SMB Share for the user and add share/ NTFS permissions
Invoke-Command -ComputerName $Server -ArgumentList $Username -ErrorAction Stop -ScriptBlock
{
param($Username)
$FindShare = (Get-SmbShare -Name Users$).Path
if($FindShare -eq $true)
{
try
{
New-Item -ItemType Directory -Path "$FindShare\$Username" -ErrorAction Stop
New-SmbShare -Name "$Username$" -Path "$FindShare\$Username" -FullAccess "AD\Server Admins", "AD\Domain Admins" -ChangeAccess "AD\$Username" -ErrorAction Stop
$Acl = Get-Acl "$FindShare\$Username"
foreach($Rule in $Acl.Access)
{
$Acl.RemoveAccessRule($Rule)
}
$Ar = New-Object system.security.accesscontrol.filesystemaccessrule("Everyone","FullControl","Allow")
$Acl.SetAccessRule($Ar)
$Acl.SetAccessRuleProtection($false, $true)
Set-Acl "$FindShare\$Username" $Acl -ErrorAction Stop
}
catch
{
Write-Warning "U drive failed to create. Error: $($_.Exception.Message)"
}
}
else
{
Write-Warning "Users$ share not found on server"
}
}
}
Have you tried using the SID?
In the second function New-BVUDrive, replace the username with SID. and use the following cmdlet to get the SID:
(Get-ADUser -Identity $SamAccountName).SID.Value
you will be able to set the ACL now, until the data will replicate you will see in the security tab the SID, but the user will be able to access the folder if he will try.
$Ar = New-Object System.Security.AccessControl.FileSystemAccessRule ($SIDIdentity, 'FullControl', ('ContainerInherit','ObjectInherit'), 'None','Allow')
Hope it will help.

Invoke-command path null?

I am working on this command to be able to view and edit a registry key remotely to a computer on a joined domain when I need to test something. In this case, I am looking at Excel's "vbawarninsg" key. This works just fine.
cls
$computername = Read-Host "Enter computer name..."
Invoke-Command -ComputerName $computername {Get-ItemProperty -Path 'REGISTRY::HKEY_USERS\xxxxxxx\Software\Policies\Microsoft\office\16.0\excel\security' } |
Select-Object PSComputerName, vbawarnings, PSParentPath | fl
$name = "vbawarnings"
The next part is to set a new value for the "vbawarnings" key using New-ItemProperty. When I assigned a variable for the -Path name it gives me an error "Cannot bind argument to parameter 'Path' because it is null."
This is the script that gives me an error
cls
$computername = Read-Host "Enter computer name..."
$registryPath = 'REGISTRY::HKEY_USERS\xxxxxxx\Software\Policies\Microsoft\office\16.0\excel\security'
Invoke-Command -ComputerName $computername {Get-ItemProperty -Path $registryPath } |
Select-Object PSComputerName, vbawarnings, PSParentPath | fl
$name = "vbawarnings"
$value = Read-Host "To modify...Enter a value"
New-ItemProperty -Path $registryPath -Name $name -Value $value `
-PropertyType DWORD -Force -Verbose | Out-Null
Any help is greatly appreciated!
In order to use a variable remotely (such as the case with Invoke-Command), you need to use the $using: variable scope:
Invoke-Command -ComputerName $cn {
Get-ItemProperty -Path $using:regPath
}
or pass it as a parameter:
Invoke-Command -ComputerName $cn {
param($path)
Get-ItemProperty -Path $path
} -ArgumentList '-path', $regPath
See this article
When you do Invoke-Command, that scriptblock gets sent to the remote server.
Invoke-Command -ComputerName $computername {Get-ItemProperty -Path $registryPath }
On the remote server, $registryPath is null even though you have it locally in your script.
So just hardcode the registry path instead:
Invoke-Command -ComputerName $computername {Get-ItemProperty -Path 'REGISTRY::HKEY_USERS\xxxxxxx\Software\Policies\Microsoft\office\16.0\excel\security' }

Pulling value of the function in to a Variable

The function Get-loggedOnUser shows 3 items, and the code can be found here LoggedInUser
Computer Name
Logged on user
Sid of the user
I would like to be able to take the results of the LoggedOnUser function (only need the logged in user) and pipe the results to the variable below called Username.
Import-Module ActiveDirectory
function Get-LoggedOnUser
$credential = Get-Credential
$computers = "MachineName"
foreach ($machine in $computers)
{
Get-LoggedOnUser -ComputerName $machine
$username = $_.LoggedOn
$sid = ((get-aduser $username).SID).Value
Invoke-Command -ComputerName $machine -Credential $credential -ScriptBlock { New-PSDrive -Name HKU -PSProvider Registry -Root Registry::HKEY_USERS; New-ItemProperty -Path "HKU:\$($args[0])\Software\Microsoft\Windows\CurrentVersion\Internet Settings" -PropertyType String -Name AutoConfigURL -Value "http://proxy.domain.com/proxies/proxy.pac" -Force } -argumentlist $sid
}
$loggedOn=Get-LoggedOnUser -computername $machine
$username = $loggedOn.LoggedOn
$sid = $loggedOn.Sid #as this is already returned from above funtion
$sid = ((get-aduser $username).sid).value #if you want to still get it from AD