PowerShell: Update Scheduled Task username and password? - powershell

I'm looking for a way to update the credentials on existing scheduled tasks on machines.
Schtasks doesn't work for AT created scheduled tasks
Win32_ScheduledJob only works for AT created jobs
Schedule.Service COM object - not sure
It appears that I can use RegisterTask and RegisterTaskDefinition to CREATE scheduled tasks but I'm not clear if I can update the existing credentials with those methods. Please advise. Thx.

I would recommend having a look at the managed TaskScheduler API, which is a .NET wrapper for the TaskScheduler COM API. It's an open-source project available on CodePlex.
http://taskscheduler.codeplex.com/
The project author has this to say about updating passwords:
If you are trying to create a task using the credentials of the
current user and you only want it to run when that user is logged in,
you need to call the RegisterTaskDefinition method as in the end of
the Complex example with the InteractiveToken parameter. If you need
to create as another specific user, then use that same method, but
supply the username, password, and set the TaskLogonType to
InteractiveTokenOrPassword or Password. There are some triggers that
are specific to a user, like the LogonTrigger where you can also
supply a user credential.
The appropriate overload for RegisterTaskDefinition is defined in TaskFolder.cs.
http://taskscheduler.codeplex.com/SourceControl/changeset/view/75611#19440
public Task RegisterTaskDefinition(string Path, TaskDefinition definition
, TaskCreation createType, string UserId, string password = null
, TaskLogonType LogonType = TaskLogonType.S4U, string sddl = null)

I had this task come up recently. I know this post is old but wanted to put this script somewhere for people to find hopefully help them. This is pure power shell so no extra setup required. I had tried using the Set-ScheduledTask but had issues setting domain user. I ended up using schtasks instead. You may need to run this script as administrator.
$domainUsername = "domain\user"
$password= "somePassword"
$tasks = Get-ScheduledTask | Where-Object { $_.Principal.UserId -eq $domainUsername }
foreach ($task in $tasks) {
$myTask = $task.TaskPath+$task.TaskName
echo changing $myTask
schtasks /change /s $env:COMPUTERNAME /tn $myTask /ru $domainUsername /rp $password
}
Here is another example of how to push out to multiple servers. THe sessionCredentials are the credentials used to connect to the servers the domain user and new password are the name and password to be set to the tasks
$sessionCredentials = Get-Credential
$taskDomainUsername = "DOMAIN\SOMEACCOUNT"
$NewPassword = "somePassword"
$computerNames = #(
"someNameServer1",
"someNameServer2"
)
$serverSession = New-CimSession -ComputerName $computerNames -Credential $sessionCredentials
$tasks = Get-ScheduledTask -CimSession $serverSession | Where-Object { $_.Principal.UserId -eq $taskDomainUsername }
foreach($task in $tasks) {
echo updating $task
$specificSession = $serverSession | Where-Object { $_.ComputerName -eq $task.PSComputerName}
$taskFullName = $task.TaskPath+$task.TaskName
Set-ScheduledTask -CimSession $specificSession -TaskName $taskFullName -User $taskDomainUsername -Password $NewPassword
}

Related

Need to run commands within a Powershell script as different user

I need to work on a fairly complicated script that involves multiple service accounts to different APIs and for the most part, the script works fine but I'm running into an issue that's bugging me like crazy. I can't run the script as a different user because the powershell running the node can only run the script as NT Authority.
So a part of my script looks like this:
Foreach ($i in $AllUsers) {
$ConID = (Get-ADUser -Identity $i -Properties ExtensionAttribute2 -Credential $svcPOSHCreds).ExtensionAttribute2
$ConDN = (get-aduser -filter {EmployeeID -eq $ManagerEID} -Credential $svcPOSHCreds).DistinguishedName
Set-ADUser -Identity $i -Manager $ConDN-Credential $svcPOSHCreds
Get-ADPrincipalGroupMembership $i -Credential $svcPOSHCreds | foreach {Remove-ADGroupMember $_ -Members $i - Confirm:$false -Credential $svcPOSHCreds}
Add-ADGroupMember -Identity 'Identity Con User' -Members $i -Credential $svcPOSHCreds
}
As you can see, I have to specify -Credential for every command I run because the user running this script doesn't have the rights to do some of the stuff.
So far I can count roughly 108 "-Credential" parameters to all of the commands between different APIs and AD...etc.
Is there a way that I can group the commands together so they utilize the same "Credentials" so I don't have to specify it each time when I run each command? I cannot specify how the script will be ran :( My limit is only to the inside of the PS1! so no "runas"...etc.
Is there a way that I can group the commands together so they utilize the same "Credentials" so I don't have to specify it each time when I run each command?
Yes, you can use the preference variable $PSDefaultParameterValues to automate this process.
Here is a simple example to demonstrate how it works, first we can define to test functions:
function Set-Something {
[CmdletBinding()]
param([pscredential] $Credential, [string] $SomeParam)
[pscustomobject]#{
SomeParam = $SomeParam
UserName = $Credential.UserName
Password = [Net.NetworkCredential]::new('', $Credential.Password).Password
}
}
function Get-Something {
[CmdletBinding()]
param([pscredential] $Credential, [string] $SomeParam)
[pscustomobject]#{
SomeParam = $SomeParam
UserName = $Credential.UserName
Password = [Net.NetworkCredential]::new('', $Credential.Password).Password
}
}
And for these 2 functions we will set their -Credential parameter as a Default Parameter:
$cred = [pscredential]::new('hello', (ConvertTo-SecureString 'world' -AsPlainText))
$PSDefaultParameterValues = #{
'Set-Something:Credential' = $cred
'Get-Something:Credential' = $cred
}
Then we can test if it works correctly, here we can assume that the $cred variable will be passed as default to both functions:
Get-Something -SomeParam 123
Set-Something -SomeParam 123
The result is:
SomeParam UserName Password
--------- -------- --------
123 hello world
123 hello world
You can also use wildcards here, so for example:
$PSDefaultParameterValues = #{
'*-Something:Credential' = $cred
}
Would target all functions with noun Something.

Maintaining user environment variables in Powershell logon script with admin privileges

I accidentally deleted 180 users from my AD and they aren't recoverable. I have recreated the accounts in AD and what not. This creates a new profile on their laptops when they login because of the new SID. I'm trying to write a script that grants them access to their old profile folder and create a shortcut on their desktop that leads there.
I've got the script working fine with one problem. The environment variables that are used, end up referring back to the admin account that runs the script. The users themselves don't have permission to change security on their old folder. I need to try and have the environment variables refer to the user yet have the privilege of an admin account to rewrite the permissions.
Here is the script so far.. I'm deploying this with Task Scheduler at the moment, which is another can of worms in that I'm not entirely understanding of the credential side of things there. I mean ideally, the task would run as a domain admin, execute the script asap, and have the script resolve the environment variables to the logged on user.
$permission = ":(OI)(CI)M"
$sam = $env:USERNAME
$folderName = "C:\Users\$($sam)"
Invoke-Expression -Command ( 'ICACLS $folderName /grant:r $sam$($permission) /t' )
$WshShell = New-Object -comObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("$Home\Desktop\Profile Backup.lnk")
$Shortcut.TargetPath = $folderName
$Shortcut.Save()
Its the $env:USERNAME and $home variables that are giving me trouble..
Or is there another way I should be tackling this problem?
You could use query session command to get the login name of the current logged on user. Then create NTAccount object based on that to retrieve SID and win32_userprofile WMI object to find out the profile path. Like this:
$m = query session | Select-String -Pattern "\>console\s*(\S*)\s"
$sam = $m.Matches[0].Groups[1].value
$acc = New-Object System.Security.Principal.NTAccount($sam)
$sid = $acc.Translate([System.Security.Principal.SecurityIdentifier]).Value
$profile = Get-CimInstance -ClassName win32_userprofile -Filter "SID='$sid'"
$folderName = $profile.LocalPath
Edit I have given it second thought over-night so I'll update the answer. You will be required to have domain admin password encrypted and then users will run the script.
It always sucks when something like this happens. I don't have a possibility to try this out, but I think the following approach would be feasible. The script asks user for password encrypts it and run the command as the user.
First phase would be to have a domain admin to encrypt his password to a file:
This is to be prepared by Domain Admin (distributed with the PS script) - I recommend changing password after the recovery is complete:
1) Read-Host -AsSecureString | ConvertFrom-SecureString | Out-File 'C:\<script_path>\admin_passwd.txt'
2) This is to be executed by user (you have to fill in the admin user id and have the password file distributed with the script). The script path can be obtained by (Get-Location).Path. I'm not adding it into the source code so you can decide how to implement it:
$permission = ":(OI)(CI)M"
$admin= "<your_admin_userid>"
$sam = $env:USERNAME
$domain = $env:UserDomain
$folderName = "C:\Users\$($sam)"
# get domain admin password
$encrypted_passwd = get-content 'C:\<script_path>\admin_passwd.txt' | ConvertTo-securestring
# Setting process invocation parameters.
$process_start_info = New-Object -TypeName System.Diagnostics.ProcessStartInfo
$process_start_info.CreateNoWindow = $true
$process_start_info.UseShellExecute = $false
$process_start_info.RedirectStandardOutput = $true
$process_start_info.RedirectStandardError = $true
$process_start_info.UserName = $admin
$process_start_info.Domain = $domain
$process_start_info.Password = $encrypted_passwd
$process_start_info.Verb = 'runas'
$process_start_info.FileName = 'ICACLS'
$process_start_info.Arguments = "$folderName /grant:r $sam$($permission) /t"
# Creating process object.
$process = New-Object -TypeName System.Diagnostics.Process
$process.StartInfo = $process_start_info
# Start the process
[Void]$process.Start()
$process.WaitForExit()
# synchronous output - captures everything
$output = $process.StandardOutput.ReadToEnd()
$output += $process.StandardError.ReadToEnd()
Write-Output $output
$WshShell = New-Object -comObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("$Home\Desktop\Profile Backup.lnk")
$Shortcut.TargetPath = $folderName
$Shortcut.Save()

Create a script which disables a Windows Account on a target host. Only Admin can execute this action

I want to create a PowerShell script which will disable the windows account, the target host name will be provided as an argument. Only admin should be able to execute this task.
This is what I have tried. Could someone please tell me if this approach is right or is there any better way to do this.
param( [Parameter(Mandatory=$true)] [String] $TargetHost ,
[Parameter(Mandatory=$true)] [String] $TargetUserName ,
[String] $User ,
[String] $Password)
# Set up a trap to properly exit on terminating exceptions
trap [Exception] {
write-error $("TRAPPED: " + $_)
exit 1
}
function DeactivateAccount($TargetHost , $TargetUserName ,$User , $Password){
$TargetHost = $TargetHost #Target Host on which windows account deactivation will be done.
$TargetUserName = $TargetUserName #User Name of Target.
$Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() #Domain name of the localhost.
$localHost = [System.Net.Dns]::GetHostName()
$localIP = [System.Net.Dns]::GetHostAddresses("$localHost")
#if TargetHost and LocalHost are same.
if($localHost -like $TargetHost -OR $localIP -like $TargetHost) {
if($Domain -eq [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()){
$process = net user $TargetUsername /domain /active:no #Performs the operation on the domain controller in the computer's primary domain.
} else {
$process = net user $TargetUsername /active:no
}
Write-host " $TargetUsername account deactivated "
}
#If TargetHost is remote Host.
else {
$User = $User #Creds to perform admin function.
$Password = $Password
$SecurePassword = new-Object System.Security.SecureString #Convert password into secure string.
$Password.ToCharArray() | % { $SecurePassword.AppendChar($_) }
$Cred = New-Object -typename System.Management.Automation.PSCredential -argumentlist "$User",$securePassword
$newSession = New-PSSession -ComputerName "$TargetHost" -credential $Cred #Used PSSession for persistent connection and credentials to Specify a user account that has permission to perform this action.
$export_username = Invoke-Command -Session $newSession -ScriptBlock {$username=args[1]} # Invoke-Command command uses the Session parameter(here newSession) to run the commands in same session.
if($Domain -eq [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()){
$process = Invoke-Command -Session $newSession -ScriptBlock {net user $username /domain /active:no}
} else {
$process = Invoke-Command -Session $newSession -ScriptBlock {net user $username /active:no}
}
Write-host " $TargetUsername account deactivated "
Remove-PSSession $newSession # Closes Windows PowerShell sessions.
}
if(-not $?) { # Returns true if last command was successful.
Write-Error "Windows Deactivation Failed!!"
exit 1
}
}
DeactivateAccount($TargetHost , $TargetUserName ,$User , $Password)
Couple of things:
Your meant to show some code to show you tried but since you're new to Powershell I'll let that slide :)
Is it a local windows account you are trying to disable or an AD one? For the purpose of this I'll assume local.
Grab this module: https://gallery.technet.microsoft.com/PowerShell-Module-to-255637a3
The dude basically made a module for exactly what you want to do :)
Note: If you have Powershell 5.1+ you won't need the module they added new cmdlets to do this natively.
Credential-wise I wouldn't worry, Powershell can't bypass windows security, it will execute with the permissions of the user that ran the script unless your script specifically gives credentials for another user in the commands.
Let me know how you get on.

Powershell Verify local Credentials

So in my script I want to not only have the user enter and store credentials in a variable but be able to verify that the password matches the admin password on the target system. So far the only way I have found to do this is by putting the actual password unecrypted in the script and comparing it to the one the user enters. That is a huge security flaw and to remedy it I was wondering if I could get the admin password using a gwmi query (SID?) as an object and compare that to the secure string the user enters.
Here is my flawed code I am using right now.
Do
{
$password = $null
$password = read-host "Enter the Administrator Password" -assecurestring
$AdminPass = ConvertTo-SecureString "adminpassword" -AsPlainText -Force
$pwd1_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
$pwd2_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($AdminPass))
if ($pwd1_text -cne $pwd2_text) {Write-Host -ForegroundColor Red "Incorrect Password"; $password = $null}
$count ++
$tries = 3 - $count
if ($password -eq $null) {Write-Host -ForegroundColor Yellow "$tries Attempts Remaining"}
if ($count -eq 3) {Write-Host -ForegroundColor Red "$count Unsuccessful Password Attempts. Exiting..."; exit}
}While ($password -eq $null)
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist "$ComputerName\Administrator",$password
Here's a function I wrote that tests a PSCredential object, against a Domain or a local Machine:
function Test-Credential {
<#
.SYNOPSIS
Takes a PSCredential object and validates it against the domain (or local machine, or ADAM instance).
.PARAMETER cred
A PScredential object with the username/password you wish to test. Typically this is generated using the Get-Credential cmdlet. Accepts pipeline input.
.PARAMETER context
An optional parameter specifying what type of credential this is. Possible values are 'Domain' for Active Directory accounts, and 'Machine' for local machine accounts. The default is 'Domain.'
.OUTPUTS
A boolean, indicating whether the credentials were successfully validated.
.NOTES
Created by Jeffrey B Smith, 6/30/2010
#>
param(
[parameter(Mandatory=$true,ValueFromPipeline=$true)]
[System.Management.Automation.PSCredential]$credential,
[parameter()][validateset('Domain','Machine')]
[string]$context = 'Domain'
)
begin {
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::$context)
}
process {
$DS.ValidateCredentials($credential.GetNetworkCredential().UserName, $credential.GetNetworkCredential().password)
}
}
If you want to test against local accounts on a remote machine, you'll need to load this function on the remote machine and test the credential against the 'local' machine via remoting (Invoke-Command), but it should be possible.

How can I programatically set the owner of a MSMQ queue?

I have a powershell script that creates some private queues for me. However, the queues default to being owned by whoever ran the install script. I'd prefer to have them owned by a group (i.e. - Administrators or some such). Anybody know how to do this?
foreach($strQueue in $strQueues) {
if (![System.Messaging.MessageQueue]::Exists($strQueue)) {
$q = [System.Messaging.MessageQueue]::Create($strQueue)
$q.SetPermissions($queueUser, [System.Messaging.MessageQueueAccessRights]::FullControl, [System.Messaging.AccessControlEntryType]::Set)
$q.SetPermissions("BUILTIN\Administrators", [System.Messaging.MessageQueueAccessRights]::TakeQueueOwnership, [System.Messaging.AccessControlEntryType]::Set)
Write-Host "... created $strQueue and set FullControl permissions for $queueUser"
}
}
I know this is an ancient question and has already been answered but I was struggling with this for far too long today and have to post my solution to spare others the pain.
If you don't have access to the MSMQ by default then you need to run the commands as an impersonated user as #Noon Silk suggested.
This code will let you create and assign permissions to the queues as a different user
$Username = "Eric"
$Password = "MyPassword"
$securePass = ConvertTo-SecureString $Password -AsPlainText -Force
$credential = New-Object Management.Automation.PSCredential($Username, $securePass)
foreach($strQueue in $strQueues) {
if (![System.Messaging.MessageQueue]::Exists($strQueue)) {
$script =
{
$q = [System.Messaging.MessageQueue]::Create($strQueue)
$q.SetPermissions($queueUser, [System.Messaging.MessageQueueAccessRights]::FullControl, [System.Messaging.AccessControlEntryType]::Set)
$q.SetPermissions("BUILTIN\Administrators", [System.Messaging.MessageQueueAccessRights]::TakeQueueOwnership, [System.Messaging.AccessControlEntryType]::Set)
Write-Host "... created $strQueue and set FullControl permissions for $queueUser"
}
Invoke-Command -Credential $credential -ScriptBlock $script
}
}
I think that taking ownership can be done only from native code ( with the c api of msmq). So no powershell here. But there is a c+ sample here.
You could try impersonating a relevant admin account before creating them ... ?