Powershell run privileged commands from non privileged user by passing credentials - powershell

i've some trouble with executing command that need more privileges than the calling user has.
I wrote a "admin repository" of powershell scripts & snippets for management reasons. I create a auto importer script and also an auto update if i release a new tag. Works like a charm!
But than we decieded to split some privileges to other users to fulfill some JEA requirements.
Now our unprivileged "working" users are importing the repository and are not allowed to run every command. (eg. querying DHCP sever)
I thought it would be no issue - thought about the Get-Credential simply running the priv features with the priv user.... but i was wrong... it's not simple as i thought.
First issue was that the command does not accept a -credential param.
i ended up with something like this:
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "powershell"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Domain = $cred.UserName.Split('\')[0]
$pinfo.Password = $cred.Password
$pinfo.UserName = $cred.UserName.Split('\')[1]
$pinfo.CreateNoWindow = $true
$pinfo.Arguments = "Get-DhcpServerv4Scope -computername $server"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
$p.WaitForExit()
it took me some time to get the output of this session to a variable...
fist i wrote output to a tmp file and red it with the other session, but it feels like I do it terrible wrong.
Now I've to parse the output, create a template and build the object again - i've to change half of the script and it become slow and I dont like the way....
I can't escape the feeling that there is a better way - so im asking you :)
Best Regards
David Bla

Invoke-Command allows the passing of credentials.
Invoke-Command -ScriptBlock {
# put your code here
Write-Host "Hello World!"
} -Credential (Get-Credential)

I switched to start-job because it allows passing credentials but not using the remoteing feature:
$variable_needs_to_be_passed
$a_job = start-job -ScriptBlock{param($var) Do-Stuff-With-Other-User $var} `
-Arg $variable_needs_to_be_passed -credentials $cred
while((get-job -Id ($a_job.id)).State -eq "Running") {sleep(0.5)}
$return_value = Recieve-Job -Id ($a_job.id)
But its terrible slow compared to running command directly (due to iterative calling) maybe it would be better to call the complete script instead of using it to execute single commands.
To better understand it, this part searches all dns and dhcp servers for a specific client.
exemplary code:
if(check_permission){ GetAllDhcpScopes }else{ runas ... GetAllDhcpScopes }
foreach( AllDhcpScopes ){ if(check_permission){ GetDhcpLeases $_ }else{ runas ... GetDhcpLeases $_ }
This was anoying in performance so i started to do a permission_check at the beginning and than run the script normally or starating it as job with higher perms!

Related

Wait for remote Powershell script to finish

I'm creating a powershell script to import Dynamics NAV Application Objects to my Dynamics NAV 2018 database.
Microsoft is providing a PS cmdlet - Import-NAVApplicationObjects - to do so, but I have trouble to wait for the command to finish.
I have a calling script which does the following
$PSSession = New-PSSession -ComputerName $TargetMachine -Credential $MyCredential
Invoke-Command -Session $PSSession -ArgumentList $Database_Name_user, $MyCredential -ScriptBlock {
$process = Start-Process powershell.exe -Verb RunAs -Wait -PassThru -ArgumentList "-File `"C:\Users\User\Desktop\Database\Import1.ps1`" $args[0]"
$process.WaitForExit()
if ($process.ExitCode -ne 0) {
throw $process.StandardError.ReadToEnd()
}
}
The script Import1.ps1 on my $TargetMachine looks like this
param(
[String]$Database_Name_user
)
try {
$AllFiles = "C:\Users\User\Documents\AllFiles.txt"
$Modules = "C:\GIT\Loading Components\NAVModuleImport.ps1"
$OutputBuffer = import-module $Modules
Import-NAVApplicationObject -Path $AllFiles -DatabaseServer "SQLSRV001" -DatabaseName $Database_Name_user -SynchronizeSchemaChanges "No" -ImportAction "Overwrite" -Confirm:$false | Out-Null
}
catch{
$_ | Out-File "C:\Users\User\Documents\Import1.txt"
}
The file AllFiles.txt has a size of 220 MB and contains more than 7700 Dynamics NAV Application Objects (tables, pages, codeunits and so on).
When I launch the script which executes Import-NAVApplicationObject directly from the remote computer stored in $TargetMachine everything works smootly and the process takes up to 10 to 15 minutes, as expected.
When calling the script as shown in the first code example the output stops for a minute and then says everything is done.
Any help is appreciated, thank you in advance.
Edit: Solution
I noticed that my scripts as shown are working, the Import-NAVApplicationObjects cmdlet just failed.
When I elevate the powershell process on the remote computer and run the import, the cmdlet tried to authenticate as NT-Authority\Anonymous to the database.
Then I passed the credentials of the user that opens the remote PSSession to Import1.ps1 and used the parameters -UserName and -Password of the import cmdlet.
Sadly this process failed again.
Now I tried some things with the user I want to use for authenticating, changing passwords etc and it worked! The password contained a semicolon ; and apparently the import cmdlet was not happy with that.
So my simple solution is: Removing the semicolon from the password.

PowerShell Computer StartUp-Script - Switch user-context?

Through Microsoft Group Policy I did define to run a Powershell-Script on Computer Start-Up. Also I have the requirement to run a Powershell-Script as Scheduled Task without saving credentials.
On both scenarios I have the same problem ...
I want to run a Citrix Powershell-Command (PSSnapIn) like:
Set-BrokerMachine -MachineName "domain.local\$env:COMPUTERNAME" -AdminAddress "RemoteServer.domain.local" -InMaintenanceMode $True
Manual: https://citrix.github.io/delivery-controller-sdk/Broker/Set-BrokerMachine/
Of course only users who have the permission could run those Citrix-commands. I would be able to give a domain-user the permission to run the command "Set-BrokerMachine", but in the mentioned scenarios the PowerShell-scripts run in context of the system-user.
I did simulate the system-user by PSExec:
Error running as System-User
My scripts do other things of course and I want to keep them running as System-User, but now I am looking for a clean solution to get those Citrix-commands running.
If possible, I don't want to save credentials in my scripts.
EDIT #1:
I would be able to workaround with the following code:
$Username = "MySpecialUser"
$Password = 'MyPassword'
$SecurePassword = ConvertTo-SecureString $Password -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential $Username, $SecurePassword
$Result = Invoke-Command -Session ( New-PSSession -ComputerName "RemoteServer.domain.local" -Credential $Credential ) -ScriptBlock {
Add-PSSnapin Citrix*
Set-BrokerMachine -MachineName "domain.local\$args" -InMaintenanceMode $True
} -ArgumentList $env:COMPUTERNAME -HideComputerName
Remove-PSSession -InstanceId $Result.RunspaceId
I don't like this because:
The code has to contain credentials (ofc I could encrypt it ...)
I have to create a permission-system for this special user in Citrix
I have to put the special-user into a local-group on every server, to allow the remote-administration (security-risk)
I don't like to use PSSession
...
Is there a better/cleaner solution? Any ideas?

Import saved AzureRMContext still requires password

I was using Save-AzureRmProfile for all my scripts to execute azure requests in parallel. I borrowed this idea from auto login to azure with powershell/
I had to update my systems to latest version (AzureRM > 4) and despite the fact that the AzureRmProfile are now AzureRmContext cmdlets I still cannot use it as before.
Scenario
Open a PS console and execute
Save-AzureRmContext -Profile (Add-AzureRmAccount) -Path myprofile.json
# List my VMs
Get-AzureRmVm
Open a second PS console
Import-AzureRmContext -Path myprofile.json
# List my VMs
Get-AzureRmVm
Get-AzureRmVM : Your Azure credentials have not been set up or have expired, please run Login-AzureRMAccount to set up your Azure credentials.
How can I reuse my profile to be loaded in parallel executions?
There's a bug in the cmdlets. Not much you can do (only downgrade).
Track it here: https://github.com/Azure/azure-powershell/issues/3954
Here are a couple of workarounds.
Simple, in memory workaround, would need to be added whenever you import a context:
$ctx = Import-AzureRmContext -Path <path-to-context>
$ctx.Context.TokenCache.Deserialize($ctx.Context.TokenCache.CacheData)
More complex workaround. This creates a permanent file, TokenCache.dat, which, if present, may allow you to avoid this problem on a machine altogether.
In a new POSH window:
$ctx = Import-AzureRmContext -Path <path-to-saved-context>
$session = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance
$cacheFile = [System.IO.Path]::Combine($session.ProfileDirectory, $session.TokenCacheFile)
if (Test-Path $cacheFile) {
$session.DataStore.CopyFile($cacheFile, ($cacheFile + ".bak"))
}
$session.DataStore.WriteFile( $cacheFile, [System.Security.Cryptography.ProtectedData]::Protect($ctx.Context.TokenCache.CacheData, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser))
$session.TokenCache = New-Object -TypeName Microsoft.Azure.Commands.Common.Authentication.ProtectedFileTokenCache -ArgumentList $cacheFile
[Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext.TokenCache = $session.TokenCache
Note that this problem should be fixed in the next release
As a workaround until the issue is not fixed or I downgrade my PS installation I used
$azureAccountName ="my.email#example.com"
$Password = "12345678"
$azurePassword = ConvertTo-SecureString $Password -AsPlainText -Force
$psCred = New-Object System.Management.Automation.PSCredential($azureAccountName, $azurePassword)
Then in my parallel ScriptBlock I do a call like this to replace the broken import credentials
Login-AzureRmAccount -Credential $psCred
Not the kind of solutions I'm proud of but... it did the trick.

Powershell start process using call operator and passing custom credentials

I'm trying to execute DbUp application using Octopus deployment.
Instruction says to use this simple script:
& .\DbUp.exe | Write-Host
But the problem with that is I couldn't find the way to pass custom credentials to process start. I tried to use Process object directly:
"DbUp Deployment Script starting." | Write-Host
$pinfo.Username = $OctopusParameters['serviceCustomAccountName']
$pinfo.Domain = "DOMAIN"
$pinfo.Password = (ConvertTo-SecureString -String $OctopusParameters['serviceCustomAccountPassword'] -AsPlainText -Force)
$pinfo.FileName = "C:\DbUp.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = ""
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start()
$p.WaitForExit()
$p.StandardOutput.ReadToEnd() | Write-Host
$p.StandardError.ReadToEnd() | Write-Host
"DbUp Deployment Script finished." | Write-Host
But this approach results with no StandardOutput being passed to Octopus log:
DbUp Deployment Script starting.
True
DbUp Deployment Script finished.
It seems to not execute the DbUp.exe at all because no changes are applied to database.
Is there a way to start process using call operator (& .\DbUp.exe) and passing custom credentials? If not, what might be the reason for Octopus not picking output form console app?
The powershell user context is associated with the user running the tentacle service, so that service can be admin and have db connection rights. The -NoNewWindow flag used with start-process should re-direct output to the same session.

Trouble calling powershell script from within powershell with arguments

I have spent the last 4 hours on this issue and would greatly appreciate any input you might have.
I need to call a powershell script with different credentials and pass arguments onto that script.
Following the installation of a program wrapped in WISEScript this script kicks off to gather AD accounts for the machine and remove them from specific AD Security Groups. Unfortunately as the script runs locally I cannot use ActiveDirectory modules in powershell as not all machines in our environment have RSAT.
The initial script is run from an elevated account on the machine:
$creds = New-Object System.Management.Automation.PsCredential("DOMAIN\USER", (ConvertTo-SecureString "Password" -AsPlainText -Force))
$ProfileGUIDS = Get-ChildItem 'hklm:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileGuid'
$Groups = [ADSI]"LDAP://CN=Group4d_test,OU=GroupMigrationTesting,OU=TestOU,OU=US,DC=DOMAIN",[ADSI]"LDAP://CN=Group3d_test,OU=GroupMigrationTesting,OU=TestOU,OU=US,DC=DOMAIN"
Function Get-DistinguishedName ($strUserName)
{
$searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]'')
$searcher.Filter = "(&(objectClass=User)(samAccountName=$strUserName))"
$result = $searcher.FindOne()
if ($result)
{
Return $result.GetDirectoryEntry().DistinguishedName
}
}
forEach ($GUIDkey in $ProfileGUIDS)
{
$GUID = Out-String -InputObject $GUIDKey
$index = $GUID.IndexOf("S-1")
$GUID = $GUID.Substring($index)
$GUID = $GUID.Substring(0,128)
$index = $GUID.IndexOf(" ")
$GUID = $GUID.Substring(0,$index)
$Profile = "hklm:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$GUID"
$ProfileItems = Get-ItemProperty $Profile
$SAM = $ProfileItems.ProfileImagePath
$index = $SAM.LastIndexOf("\")
$index ++
$SAM = $SAM.substring($index)
$UserDN = Get-DistinguishedName $SAM
$User = [ADSI]"LDAP://$UserDN"
if($User -ne $null)
{
forEach($group in $groups)
{
Right here is where I need to call the 2nd script with different credentials.
This is RemoveUsers.ps1, the script I need to run with different credentials:
param
(
[string]$group = "MyDefaultSAM",
[string]$user = "MyDefaultUser"
)
$Group.remove($User.ADsPath)
I have tried:
start-process powershell.exe -Credential $creds -NoNewWindow -ArgumentList "Start-Process $PSSCriptRoot\RemoveUsers.ps1 -Verb
This will run the script however I cannot specify any arguments
powershell.exe -file "$PSScriptRoot\RemoveUsers.ps1" -user $user -group $group
This calls the script with arguments but does not allow for the -Credentials switch
I have also tried:
$job = Start-Job -ScriptBlock {
powershell.exe -file "$PSScriptRoot\RemoveUsers.ps1" -user $user -group $group
} -Credential $creds
This runs but does not appear to work properly as the users remain in the AD groups.
Any help is appreciated.
Thanks - Jeff
**** UPDATE ****
Thanks for the information. When I add the changes you suggest I receive an error
Invoke-Command : Parameter set cannot be resolved using the specified named parameters
It appears, as I have found online, the -Credential switch cannot be used without the -Computer switch. If I specify $env:COMPUTERNAME or localhost for the computer I receive the error
\RemoveUsers.ps1 is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was
included, verify that the path is correct and try again
I can avoid this issue if I remove the -Credential switch and open the AD group to everyone. At this point I don't need to elevate a new powershell script and can add the command in the same. If I cannot resolve the issue with Invoke-Command this is likely what I will do.
**** UPDATE ****
What I ultimately had to do was use -Authentication Credssp in the argument list as there is an issue with using the AD Module via Invoke-Command. In addition I had to start the Win-RM service, Enable WSMacCredSSP (-role client on each machine and add a DelegateComputer entry and -role server on the server connecting to). Only after the service was started and an entry was made for WSManCredSSP was I able to use the Invoke-Command switch and have the AD Module work correctly.
This of course makes things more complicated and I decided just installing the AD Module on each PC (after finding a way to do it without RSAT) and forgetting about running the command remotely all together. Thanks for your help with the matter.
Thanks
You don't need to run PowerShell scripts with powershell.exe when calling them from another PowerShell script. Simply use the call operator (&). Also, I'd use Invoke-Command for running something inline with different credentials.
Beware that the scriptblock doesn't automatically know about the variables in the rest of your script. You need to pass them into the scriptblock via the -ArgumentList parameter. That is most likely the reason why removal didn't work when you ran RemoveUsers.ps1 as a job.
Something like this should work:
Invoke-Command -ScriptBlock {
& "$PSScriptRoot\RemoveUsers.ps1" -user $args[0] -group $args[1]
} -ArgumentList $user, $group -Credential $creds -Computer $env:COMPUTERNAME
This requires PSRemoting, though (run Enable-PSRemoting as an administrator).