Post-Rotation scripts for updating the "Log on as" credentials of a Windows Service
A common use case is updating the "Log on as" credentials of a Windows service and restarting the service. Below, are several use case examples and exercises for you to review and follow along with.
PAM script for updating the "Log on as" credentials of a Windows service
Overview
The following PowerShell code examples updates the "Log on as" credentials of a Windows Service after its password has been rotated via Keeper Rotation. These scripts are the preferred method of performing a "Log on as" credential update on either the local Keeper Gateway host or target Windows Based systems.
Example 1:
In this example, we will be using the "Invoke-Method" over an established "PSSession" to a Windows based target system and utilizes the native PowerShell cmdlets for managing services. In this case we are using the "Microsoft.PowerShell.Management" and its sub-commands like, "Get-Service" or "Set-Service", to manage the "Log on as" credentials of a target Windows Service.
Step 1:
Expand and copy the PowerShell script, below, into a ",ps1" filename of your choosing. In this example, we named the script filename "rotate_service_cred.ps1". Attach the script to the "PAM Scripts" section of the PAM User record. Include two additional "Rotation Credential" records by selecting the current PAM User record and the Administrative Credential record that will run the script. The Administrative Credential can be the same one used in the "Password Rotation Settings" or another record which has credentials with the correct administrative permissions to run the script and its actions.
PowerShell Script using Invoke-Method over PSSession and Microsoft.PowerShell.Management
[CmdletBinding()]param ( [Parameter(ValueFromPipeline=$true)] [string] $Record)try {# This Section brings in the PAM User Record InformationWrite-Debug"Decoding and converting the PAM User Record Information from Base64" $RecordJsonAsB64 = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Record))if (-not $RecordJsonAsB64) {throw"Failed to decode the PAM User Record Information from Base64." }Write-Debug"Converting the decoded JSON to PowerShell object" $RecordParams = $RecordJsonAsB64 |ConvertFrom-Jsonif (-not $RecordParams) {throw"Failed to convert the decoded JSON to PowerShell object." }Write-Debug"PAM User Record Information successfully retrieved and converted."}catch {Write-Error"An error occurred while processing the PAM User Record Information: $_"}finally {Write-Debug"Completed processing the PAM User Record Information."}# End of Section.try {# This Section brings in ALL associated Records and their Parameter InformationWrite-Debug"Decoding and converting all associated records from Base64" $recordsJSON = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($RecordParams.records))if (-not $recordsJSON) {throw"Failed to decode the associated records from Base64." }Write-Debug"Converting the decoded JSON to PowerShell object" $records = $recordsJSON |ConvertFrom-Jsonif (-not $records) {throw"Failed to convert the decoded JSON to PowerShell object." }Write-Debug"Associated records successfully retrieved and converted."}catch {Write-Error"An error occurred while processing the associated records: $_"}finally {Write-Debug"Completed processing the associated records."}# End of Section.try {# This Section Defines parameters from the User Record. "remotecomp" and "service" are coming from custom fields in the Pam User RecordWrite-Debug"Defining parameters from the User Record" $ErrorActionPreference ='Stop' $DebugPreference ='Continue' $remoteComputer = ($records |Where-Object {$_.uid-eq $RecordParams.userRecordUid}).remotecompif (-not $remoteComputer) {throw"Failed to retrieve 'remotecomp' from the User Record." } $serviceName = ($records |Where-Object {$_.uid-eq $RecordParams.userRecordUid}).serviceif (-not $serviceName) {throw"Failed to retrieve 'service' from the User Record." } $user = ($RecordParams.user)if (-not $user) {throw"Failed to retrieve 'user' from the User Record." } $newPassword = ($RecordParams.newPassword)if (-not $newPassword) {throw"Failed to retrieve 'newPassword' from the User Record." }Write-Debug"Parameters from the User Record successfully defined."}catch {Write-Error"An error occurred while defining parameters from the User Record: $_"}finally {Write-Debug"Completed defining parameters from the User Record."}# End of Section.try {# This Section Defines your AD Administrative credentials from the Resource RecordWrite-Debug"Defining AD Administrative credentials from the Resource Record" $adAdminUser = ($records |Where-Object {$_.uid-eq $RecordParams.resourceRecordUid}).loginif (-not $adAdminUser) {throw"Failed to retrieve 'login' from the Resource Record." } $adAdminPassword = ($records |Where-Object {$_.uid-eq $RecordParams.resourceRecordUid}).passwordif (-not $adAdminPassword) {throw"Failed to retrieve 'password' from the Resource Record." }Write-Debug"AD Administrative credentials successfully defined."}catch {Write-Error"An error occurred while defining AD Administrative credentials from the Resource Record: $_"}finally {Write-Debug"Completed defining AD Administrative credentials from the Resource Record."}# End of Section.# The above calls the Resrouce Record NOT any attached record to the script. Below is an example of calling out an atttached record to the script.# $adAdminUser = ($records | Where-Object {$_.uid -eq "UID of Record Here"}).login# $adAdminPassword = ($records | Where-Object {$_.uid -eq "UID of Record Here"}).passwordtry {# This Section further secures the AD administrative credentials into a Secure StringWrite-Debug"Securing the AD administrative credentials into a Secure String" $securePassword =ConvertTo-SecureString $adAdminPassword -AsPlainText -Forceif (-not $securePassword) {throw"Failed to convert the administrative password to a Secure String." } $credential =New-Object System.Management.Automation.PSCredential ("$adAdminUser", $securePassword)if (-not $credential) {throw"Failed to create PSCredential object." }Write-Debug"AD administrative credentials successfully secured and PSCredential object created."}catch {Write-Error"An error occurred while securing AD administrative credentials: $_"}finally {Write-Debug"Completed securing AD administrative credentials."}# End of Section.try {# This Section creates a new PSSession with the AD Administrative Resource RecordWrite-Debug"Creating a new PSSession with the AD Administrative Resource Record" $session =New-PSSession-ComputerName $remoteComputer -Credential $credential -ErrorAction Stopif (-not $session) {throw"Failed to create a new PSSession." }Write-Debug"PSSession created successfully." }catch {Write-Error"An error occurred while creating the PSSession: $_" }finally {Write-Debug"Completed the attempt to create a new PSSession." }# End of Section.try {# This Script Block checks the status of the service on the remote computerWrite-Debug"Checking the status of $serviceName" $serviceStatus =Invoke-Command-Session $session -ScriptBlock {param ($serviceName)try { $service =Get-Service-Name $serviceName -ErrorAction Stopreturn $service.Status }catch {throw"An error occurred while retrieving the service status: $_" } } -ArgumentList $serviceNameif ($serviceStatus -eq'Stopped') {Write-Debug"$serviceName is already stopped. Skipping the stop service script." }else {try {# This Script Block stops the service on the remote computerWrite-Debug"Stopping $serviceName" $stopServiceResult =Invoke-Command-Session $session -ScriptBlock {param ($serviceName)try {Stop-Service-Name $serviceName -Force -ErrorAction Stop $service =Get-Service-Name $serviceNameif ($service.Status -ne'Stopped') {throw"Service did not stop successfully. Current status: $($service.Status)" }Write-Output"Service stopped successfully" }catch {throw"An error occurred while stopping the service: $_" } } -ArgumentList $serviceNameif ($stopServiceResult -ne"Service stopped successfully") {throw"Service stop operation did not return success." } }catch {Write-Error"An error occurred while stopping the service: $_" } } }catch {Write-Error"An error occurred while checking or stopping the service: $_" }finally {Write-Debug"Completed checking and stopping the service." }try {# This Script Block Updates the service password on the remote computerWrite-Debug"Changing $serviceName password" $passwordChangeResult =Invoke-Command-Session $session -ScriptBlock {param ($serviceName, $user, $newPassword)try { $service =Get-WmiObject-Class Win32_Service -Filter "Name='$serviceName'"-ErrorAction Stop $result = $service.change($null,$null,$null,$null,$null,$null,"$user","$newPassword")if ($result.ReturnValue -eq0) {Write-Output"Password changed successfully" }else {throw"Failed to change password with return value $($result.ReturnValue)" } }catch {throw"An error occurred while changing the service password: $_" } } -ArgumentList $serviceName, $user, $newPasswordif ($passwordChangeResult -ne"Password changed successfully") {throw"Password change operation did not return success." } }catch {Write-Error"An error occurred while changing the service password: $_" }finally {Write-Debug"Completed changing the service password." }try {# This Script Block Starts the service on the remote computerWrite-Debug"Restarting $serviceName" $startServiceResult =Invoke-Command-Session $session -ScriptBlock {param ($serviceName)try {Start-Service-Name $serviceName -ErrorAction Stop $service =Get-Service-Name $serviceNameif ($service.Status -ne'Running') {throw"Service did not start successfully. Current status: $($service.Status)" }Write-Output"Service started successfully" }catch {throw"An error occurred while starting the service: $_" } } -ArgumentList $serviceNameif ($startServiceResult -ne"Service started successfully") {throw"Service start operation did not return success." } }catch {Write-Error"An error occurred while restarting the service: $_" }finally {Write-Debug"Completed attempting to restart the service." }# End Script Block.# This Sections Removes the PSSessionRemove-PSSession-Session $session# End of Section
The resulting PAM Script configuration screen is below:
Step 2:
Add two custom text fields, to the PAM User record, called "remotecomp" and "service". In the "remotecomp" field provide the DNS name of the target system which is running the service. For this example "EC2AMAZ-A0GBNDM" is the name of the target system. If the Windows Service you are managing is local to the Keeper Gateway Host use, "localhost" for the "remotecomp" field. In the "service" field, provide the "Service Name" of the service and not the the "Display Name". In this instance and example, the "Service Name" is SNMPTRAP.
Here's the resulting Record configuration in the vault:
Example 2:
In this example, we will be using the "Service Control" over "PSSession" to a Windows based target system and utilize the native PowerShell cmdlets for managing services. In this case we are using the "Service Control" and its sub-commands like, "sc.exe stop SNMPTRAP" or "sc.exe start SNMPTRAP", to manage the "Log on as" credentials of a target Windows Service.
Step 1:
Expand and copy the PowerShell script, below, into a ",ps1" filename of your choosing. In this example, we named the script filename "rotate_service_cred.ps1". Attach the script to the "PAM Scripts" section of the PAM User record. Include two additional "Rotation Credential" records by selecting the current PAM User record and the Administrative Credential record that will run the script. The Administrative Credential can be the same one used in the "Password Rotation Settings" or another record which has credentials with the correct administrative permissions to run the script and its actions.
PowerShell Script using Invoke-Method over PSSession and Service Control
[CmdletBinding()]param ( [Parameter(ValueFromPipeline=$true)] [string] $Record)try {# This Section brings in the PAM User Record InformationWrite-Debug"Decoding and converting the PAM User Record Information from Base64" $RecordJsonAsB64 = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Record))if (-not $RecordJsonAsB64) {throw"Failed to decode the PAM User Record Information from Base64." }Write-Debug"Converting the decoded JSON to PowerShell object" $RecordParams = $RecordJsonAsB64 |ConvertFrom-Jsonif (-not $RecordParams) {throw"Failed to convert the decoded JSON to PowerShell object." }Write-Debug"PAM User Record Information successfully retrieved and converted."}catch {Write-Error"An error occurred while processing the PAM User Record Information: $_"}finally {Write-Debug"Completed processing the PAM User Record Information."}# End of Section.try {# This Section brings in ALL associated Records and their Parameter InformationWrite-Debug"Decoding and converting all associated records from Base64" $recordsJSON = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($RecordParams.records))if (-not $recordsJSON) {throw"Failed to decode the associated records from Base64." }Write-Debug"Converting the decoded JSON to PowerShell object" $records = $recordsJSON |ConvertFrom-Jsonif (-not $records) {throw"Failed to convert the decoded JSON to PowerShell object." }Write-Debug"Associated records successfully retrieved and converted."}catch {Write-Error"An error occurred while processing the associated records: $_"}finally {Write-Debug"Completed processing the associated records."}# End of Section.try {# This Section Defines parameters from the User Record. "remotecomp" and "service" are coming from custom fields in the Pam User RecordWrite-Debug"Defining parameters from the User Record" $ErrorActionPreference ='Stop' $DebugPreference ='Continue' $remoteComputer = ($records |Where-Object {$_.uid-eq $RecordParams.userRecordUid}).remotecompif (-not $remoteComputer) {throw"Failed to retrieve 'remotecomp' from the User Record." } $serviceName = ($records |Where-Object {$_.uid-eq $RecordParams.userRecordUid}).serviceif (-not $serviceName) {throw"Failed to retrieve 'service' from the User Record." } $user = ($RecordParams.user)if (-not $user) {throw"Failed to retrieve 'user' from the User Record." } $newPassword = ($RecordParams.newPassword)if (-not $newPassword) {throw"Failed to retrieve 'newPassword' from the User Record." }Write-Debug"Parameters from the User Record successfully defined."}catch {Write-Error"An error occurred while defining parameters from the User Record: $_"}finally {Write-Debug"Completed defining parameters from the User Record."}# End of Section.try {# This Section Defines your AD Administrative credentials from the Resource RecordWrite-Debug"Defining AD Administrative credentials from the Resource Record" $adAdminUser = ($records |Where-Object {$_.uid-eq $RecordParams.resourceRecordUid}).loginif (-not $adAdminUser) {throw"Failed to retrieve 'login' from the Resource Record." } $adAdminPassword = ($records |Where-Object {$_.uid-eq $RecordParams.resourceRecordUid}).passwordif (-not $adAdminPassword) {throw"Failed to retrieve 'password' from the Resource Record." }Write-Debug"AD Administrative credentials successfully defined."}catch {Write-Error"An error occurred while defining AD Administrative credentials from the Resource Record: $_"}finally {Write-Debug"Completed defining AD Administrative credentials from the Resource Record."}# End of Section.# The above calls the Resrouce Record NOT any attached record to the script. Below is an example of calling out an atttached record to the script.# $adAdminUser = ($records | Where-Object {$_.uid -eq "UID of Record Here"}).login# $adAdminPassword = ($records | Where-Object {$_.uid -eq "UID of Record Here"}).passwordtry {# This Section further secures the AD administrative credentials into a Secure StringWrite-Debug"Securing the AD administrative credentials into a Secure String" $securePassword =ConvertTo-SecureString $adAdminPassword -AsPlainText -Forceif (-not $securePassword) {throw"Failed to convert the administrative password to a Secure String." } $credential =New-Object System.Management.Automation.PSCredential ("$adAdminUser", $securePassword)if (-not $credential) {throw"Failed to create PSCredential object." }Write-Debug"AD administrative credentials successfully secured and PSCredential object created."}catch {Write-Error"An error occurred while securing AD administrative credentials: $_"}finally {Write-Debug"Completed securing AD administrative credentials."}# End of Section.try {# This Section creates a new PSSession with the AD Administrative Resource RecordWrite-Debug"Creating a new PSSession with the AD Administrative Resource Record" $session =New-PSSession-ComputerName $remoteComputer -Credential $credential -ErrorAction Stopif (-not $session) {throw"Failed to create a new PSSession." }Write-Debug"PSSession created successfully." }catch {Write-Error"An error occurred while creating the PSSession: $_" }finally {Write-Debug"Completed the attempt to create a new PSSession." }# End of Section.# This Script Block stops the service.Invoke-Command-Session $session -ScriptBlock {param ($serviceName)# Check if the service is already stopped $service =Get-Service-Name $serviceNameif ($service.Status -eq'Stopped') {Write-Debug"$serviceName is already stopped." } else {Write-Debug"Stopping $serviceName"sc.exe stop $serviceName# Check if the stop command was successfulif ($LASTEXITCODE-eq0) {Write-Debug"$serviceName stopped successfully." } else {Write-Error"Failed to stop $serviceName. Exit code: $LASTEXITCODE"return# Script Block exiting }# Sleep for 5 seconds to allow service to stop.Start-Sleep-Seconds 5 } } -ArgumentList $serviceName# End of Script Block.# Attempt to change the service passwordInvoke-Command-Session $session -ScriptBlock {param ($user, $newPassword, $serviceName)Write-Debug"Changing password for $serviceName"sc.exe config $serviceName obj= $user password= $newPassword# Check if the config command was successfulif ($LASTEXITCODE-eq0) {Write-Debug"Password for $serviceName changed successfully." } else {Write-Error"Failed to change password for $serviceName. Exit code: $LASTEXITCODE"return# Script Block exiting }# Sleep for 5 seconds to allow the change to take effectStart-Sleep-Seconds 5} -ArgumentList $user, $newPassword, $serviceName# End of Script Block.# Attempt to start/restart the serviceInvoke-Command-Session $session -ScriptBlock {param ($serviceName)Write-Debug"Starting $serviceName"sc.exe start $serviceName# Check if the start command was successfulif ($LASTEXITCODE-eq0) {Write-Debug"$serviceName started successfully." } else {Write-Error"Failed to start $serviceName. Exit code: $LASTEXITCODE"return# Script Block exiting }# Sleep for 5 seconds to allow the service to startStart-Sleep-Seconds 5} -ArgumentList $serviceName# End Script Block.# This Sections Removes the PSSessionRemove-PSSession-Session $session# End of Section
Step 2:
Add two custom text fields, to the PAM User record, called "remotecomp" and "service". In the "remotecomp" field provide the DNS name of the target system which is running the service. For this example "EC2AMAZ-A0GBNDM" is the name of the target system. If the Windows Service you are managing is local to the Keeper Gateway Host use, "localhost" for the "remotecomp" field. In the "service" field, provide the "Service Name" of the service and not the the "Display Name". In this instance and example, the "Service Name" is SNMPTRAP.
Here's the resulting Record configuration in the vault:
Running the Script:
Great! If you are following along, just like with Keeper Password Rotations, you can utilize the "Rotate Now" button to run it "On-Demand" and test it out as well as set a schedule for the script in the "Password Rotation Settings"
PowerShell 7 Example over SSH
PAM script for updating the "Log on as" credentials of a Windows service.
PowerShell 7 and OpenSSH are required for this setup. If you have not installed PowerShell 7 on your Keeper Gateway host system and target Windows Based systems, please visit PowerShell 7 on Windows and OpenSSH on Windows for instructions.
To run this script, SSH key based authentication must be set up and enabled between the Keeper Gateway host system and the target systems. If you have not setup your key pair authentication, please visit the URL below for instructions.
The following PowerShell code example updates the "Log on as" credentials of a Windows Service after its password has been rotated via Keeper Rotation over an established SSH session using PowerShell 7.
Example:
In this example, we will be using the "Invoke-Method" over an established "SSH" session to a Windows based target system and utilizes the native PowerShell 7 cmdlets for managing services. In this case we are using the "Microsoft.PowerShell.Management" and its sub-commands like, "Get-Service" or "Set-Service", to manage the "Log on as" credentials of a target Windows Service.
Step 1:
Expand and copy the PowerShell script, below, into a ",ps1" filename of your choosing. In this example, we named the script filename "rotate_service_cred.ps1". Attach the script to the "PAM Scripts" section of the PAM User record. Include one additional "Rotation Credential" record by selecting the current PAM User record. Check the box "Run with Command Prefix" and point to the path of your PowerShell 7 executable. This is a MUST and requirement for this script to function.
PowerShell Script using Invoke-Method over SSH
[CmdletBinding()]param ( [Parameter(ValueFromPipeline=$true)] [string] $Record)try {# This Section brings in the PAM User Record InformationWrite-Debug"Decoding and converting the PAM User Record Information from Base64" $RecordJsonAsB64 = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Record))if (-not $RecordJsonAsB64) {throw"Failed to decode the PAM User Record Information from Base64." }Write-Debug"Converting the decoded JSON to PowerShell object" $RecordParams = $RecordJsonAsB64 |ConvertFrom-Jsonif (-not $RecordParams) {throw"Failed to convert the decoded JSON to PowerShell object." }Write-Debug"PAM User Record Information successfully retrieved and converted."}catch {Write-Error"An error occurred while processing the PAM User Record Information: $_"}finally {Write-Debug"Completed processing the PAM User Record Information."}# End of Section.try {# This Section brings in ALL associated Records and their Parameter InformationWrite-Debug"Decoding and converting all associated records from Base64" $recordsJSON = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($RecordParams.records))if (-not $recordsJSON) {throw"Failed to decode the associated records from Base64." }Write-Debug"Converting the decoded JSON to PowerShell object" $records = $recordsJSON |ConvertFrom-Jsonif (-not $records) {throw"Failed to convert the decoded JSON to PowerShell object." }Write-Debug"Associated records successfully retrieved and converted."}catch {Write-Error"An error occurred while processing the associated records: $_"}finally {Write-Debug"Completed processing the associated records."}# End of Section.try {# This Section Defines parameters from the User Record. "remotecomp" and "service" are coming from custom fields in the Pam User RecordWrite-Debug"Defining parameters from the User Record" $ErrorActionPreference ='Stop' $DebugPreference ='Continue' $remoteComputer = ($records |Where-Object {$_.uid-eq $RecordParams.userRecordUid}).remotecompif (-not $remoteComputer) {throw"Failed to retrieve 'remotecomp' from the User Record." } $serviceName = ($records |Where-Object {$_.uid-eq $RecordParams.userRecordUid}).serviceif (-not $serviceName) {throw"Failed to retrieve 'service' from the User Record." } $user = ($RecordParams.user)if (-not $user) {throw"Failed to retrieve 'user' from the User Record." } $newPassword = ($RecordParams.newPassword)if (-not $newPassword) {throw"Failed to retrieve 'newPassword' from the User Record." }Write-Debug"Parameters from the User Record successfully defined."}catch {Write-Error"An error occurred while defining parameters from the User Record: $_"}finally {Write-Debug"Completed defining parameters from the User Record."}# End of Section.try {# This Section creates a new PSSession with the AD Administrative Resource RecordWrite-Debug"Creating a new SSH Session with the AD Administrative Resource Record" $session =New-PSSession-HostName $remoteComputer -UserName $user -ErrorAction Stopif (-not $session) {throw"Failed to create a new SSH Session." }Write-Debug"SSH Session created successfully." }catch {Write-Error"An error occurred while creating the SSH Session: $_" }finally {Write-Debug"Completed the attempt to create a new SSH Session." }# End of Section.try {# This Script Block checks the status of the service on the remote computerWrite-Debug"Checking the status of $serviceName" $serviceStatus =Invoke-Command-Session $session -ScriptBlock {param ($serviceName)try { $service =Get-Service-Name $serviceName -ErrorAction Stopreturn $service.Status }catch {throw"An error occurred while retrieving the service status: $_" } } -ArgumentList $serviceNameif ($serviceStatus -eq'Stopped') {Write-Debug"$serviceName is already stopped. Skipping the stop service script." }else {try {# This Script Block stops the service on the remote computerWrite-Debug"Stopping $serviceName" $stopServiceResult =Invoke-Command-Session $session -ScriptBlock {param ($serviceName)try {Stop-Service-Name $serviceName -Force -ErrorAction Stop $service =Get-Service-Name $serviceNameif ($service.Status -ne'Stopped') {throw"Service did not stop successfully. Current status: $($service.Status)" }Write-Output"Service stopped successfully" }catch {throw"An error occurred while stopping the service: $_" } } -ArgumentList $serviceNameif ($stopServiceResult -ne"Service stopped successfully") {throw"Service stop operation did not return success." } }catch {Write-Error"An error occurred while stopping the service: $_" } } }catch {Write-Error"An error occurred while checking or stopping the service: $_" }finally {Write-Debug"Completed checking and stopping the service." }try {# This Script Block Updates the service password on the remote computerWrite-Debug"Changing $serviceName password" $passwordChangeResult =Invoke-Command-Session $session -ScriptBlock {param ($serviceName, $user, $newPassword)try { $service =Get-CimInstance-ClassName Win32_Service -Filter "Name='$serviceName'"-ErrorAction Stop $result =Invoke-CimMethod-InputObject $service -MethodName Change -Arguments @{StartName=$user; StartPassword=$newPassword}if ($result.ReturnValue -eq0) {Write-Output"Password changed successfully" }else {throw"Failed to change password with return value $($result.ReturnValue)" } }catch {throw"An error occurred while changing the service password: $_" } } -ArgumentList $serviceName, $user, $newPasswordif ($passwordChangeResult -ne"Password changed successfully") {throw"Password change operation did not return success." } }catch {Write-Error"An error occurred while changing the service password: $_" }finally {Write-Debug"Completed changing the service password." }try {# This Script Block Starts the service on the remote computerWrite-Debug"Restarting $serviceName" $startServiceResult =Invoke-Command-Session $session -ScriptBlock {param ($serviceName)try {Start-Service-Name $serviceName -ErrorAction Stop $service =Get-Service-Name $serviceNameif ($service.Status -ne'Running') {throw"Service did not start successfully. Current status: $($service.Status)" }Write-Output"Service started successfully" }catch {throw"An error occurred while starting the service: $_" } } -ArgumentList $serviceNameif ($startServiceResult -ne"Service started successfully") {throw"Service start operation did not return success." } }catch {Write-Error"An error occurred while restarting the service: $_" }finally {Write-Debug"Completed attempting to restart the service." }# End Script Block.# This Sections Removes the SSH SessionRemove-PSSession-Session $session# End of Section
The resulting PAM Script configuration screen is below:
Step 2:
Add two custom text fields, to the PAM User record, called "remotecomp" and "service". In the "remotecomp" field provide the DNS name of the target system which is running the service. For this example "EC2AMAZ-A0GBNDM" is the name of the target system. If the Windows Service you are managing is local to the Keeper Gateway Host use, "localhost" for the "remotecomp" field. In the "service" field, provide the "Service Name" of the service and not the the "Display Name". In this instance and example, the "Service Name" is SNMPTRAP.
Here's the resulting Record configuration in the vault:
Running the Script:
Great! If you are following along, just like with Keeper Password Rotations, you can utilize the "Rotate Now" button to run it "On-Demand" and test it out as well as set a schedule for the script in the "Password Rotation Settings"
PowerShell Example over RPC
PAM script for updating the "Log on as" credentials of a Windows service using Service Control directly
Overview
The following PowerShell code example updates the "Log on as" credentials of a Windows Service after its password has been rotated via Keeper Rotation using Service Control (sc.exe) directly.
The recommended method is using the PowerShell Examples using Invoke-Method. With the recommended method, you can Invoke Service Control (se.exe) directly on the target system with an established PSSsessoin to that target system.
Example:
In this example, we will be using the "Service Control" to a Windows based target system and utilize the native (sc.exe)l cmdlets like, "Get-Service" or "Set-Service", to manage the "Log on as" credentials of a target Windows Service.
Step 1:
Expand and copy the PowerShell script, below, into a ",ps1" filename of your choosing. In this example, we named the script filename "rotate_service_cred.ps1". Attach the script to the "PAM Scripts" section of the PAM User record. Include one additional "Rotation Credential" record by selecting the current PAM User record.
PowerShell Script using Service Control (sc.exe) directly.
[CmdletBinding()]param ( [Parameter(ValueFromPipeline=$true)] [string] $Record)try {# This Section brings in the PAM User Record InformationWrite-Debug"Decoding and converting the PAM User Record Information from Base64" $RecordJsonAsB64 = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Record))if (-not $RecordJsonAsB64) {throw"Failed to decode the PAM User Record Information from Base64." }Write-Debug"Converting the decoded JSON to PowerShell object" $RecordParams = $RecordJsonAsB64 |ConvertFrom-Jsonif (-not $RecordParams) {throw"Failed to convert the decoded JSON to PowerShell object." }Write-Debug"PAM User Record Information successfully retrieved and converted."}catch {Write-Error"An error occurred while processing the PAM User Record Information: $_"}finally {Write-Debug"Completed processing the PAM User Record Information."}# End of Section.try {# This Section brings in ALL associated Records, attached records to the script and their Parameter InformationWrite-Debug"Decoding and converting all associated records from Base64" $recordsJSON = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($RecordParams.records))if (-not $recordsJSON) {throw"Failed to decode the associated records from Base64." }Write-Debug"Converting the decoded JSON to PowerShell object" $records = $recordsJSON |ConvertFrom-Jsonif (-not $records) {throw"Failed to convert the decoded JSON to PowerShell object." }Write-Debug"Associated records successfully retrieved and converted."}catch {Write-Error"An error occurred while processing the associated records: $_"}finally {Write-Debug"Completed processing the associated records."}# End of Section.try {# This Section Defines parameters from the User Record. "remotecomp" and "service" are coming from custom fields in the Pam User RecordWrite-Debug"Defining parameters from the User Record" $ErrorActionPreference ='Stop' $DebugPreference ='Continue' $remoteComputer = ($records |Where-Object {$_.uid-eq $RecordParams.userRecordUid}).remotecompif (-not $remoteComputer) {throw"Failed to retrieve 'remotecomp' from the User Record." } $serviceName = ($records |Where-Object {$_.uid-eq $RecordParams.userRecordUid}).serviceif (-not $serviceName) {throw"Failed to retrieve 'service' from the User Record." } $user = ($RecordParams.user)if (-not $user) {throw"Failed to retrieve 'user' from the User Record." } $newPassword = ($RecordParams.newPassword)if (-not $newPassword) {throw"Failed to retrieve 'newPassword' from the User Record." }Write-Debug"Parameters from the User Record successfully defined."}catch {Write-Error"An error occurred while defining parameters from the User Record: $_"}finally {Write-Debug"Completed defining parameters from the User Record."}# End of Section.Write-Debug"Running Post-Rotation Script on = $($RecordParams.userRecordUid)"# Attempt to stop the serviceWrite-Debug"Stopping $serviceName"try {sc.exe \\$remoteComputer stop $serviceNameWrite-Debug"$serviceName stop command executed."# Wait for the service to stopStart-Sleep-Seconds 5} catch {Write-Error"Failed to stop $serviceName. Error: $_"exit1}# Attempt to change the service passwordWrite-Debug"Changing $serviceName password"try {sc.exe \\$remoteComputer config $serviceName obj= $user password= $newPasswordWrite-Debug"$serviceName password change command executed."# Wait after changing the passwordStart-Sleep-Seconds 5} catch {Write-Error"Failed to change password for $serviceName. Error: $_"exit1}# Attempt to restart the serviceWrite-Debug"Restarting $serviceName"try {sc.exe \\$remoteComputer start $serviceNameWrite-Debug"$serviceName start command executed."} catch {Write-Error"Failed to start $serviceName. Error: $_"exit1}# Completion messageWrite-Debug"$serviceName operations completed successfully."
The resulting PAM Script configuration screen is below:
Step 2:
Add two custom text fields, to the PAM User record, called "remotecomp" and "service". In the "remotecomp" field provide the DNS name of the target system which is running the service. For this example "EC2AMAZ-A0GBNDM" is the name of the target system. If the Windows Service you are managing is local to the Keeper Gateway Host use, "localhost" for the "remotecomp" field. In the "service" field, provide the "Service Name" of the service and not the the "Display Name". In this instance and example, the "Service Name" is SNMPTRAP.
Here's the resulting Record configuration in the vault:
Running the Script:
Great! If you are following along, just like with Keeper Password Rotations, you can utilize the "Rotate Now" button to run it "On-Demand" and test it out as well as set a schedule for the script in the "Password Rotation Settings"
Batch Example via WinRPC
PAM Script example using Batch scripting to update a Windows service account credential
Overview
This code example uses standard Batch scripting to update a Windows service account credential and restart the service. The way particular example will only work on a Keeper Gateway that is running on the local server. The recommended method is using the WinRM script.
Important notes:
The rotation settings of the record must disable "symbols" because of batch scripting and escaping issues with certain symbols.
The server parameter needs to be updated with the server name or IP
Note: Server hostnames should start with a double backslash
Batch Script
@echooff:: Set the server name and service name as variablesset server=\\your-serverset service=SERVICENAMEfor /f "tokens=*" %%a in ('more') doset input=%%aset base64tmp=%temp%\base64.tmpset json=%temp%\json.tmpecho %input% > %base64tmp%certutil -decode %base64tmp% %json%for /f "usebackq delims=" %%a in (`jq -r .user %json%`) doset"user=%%a"for /f "usebackq delims=" %%a in (`jq -r .newPassword %json%`) doset"newPassword=%%a"del %base64tmp%del %json%echo"Stopping..."sc"%server%"query"%service%"|find"STOPPED"iferrorlevel1 (echo"Stopping"sc"%server%" stop "%service%")echo"Waiting for service to stop"set count=1set limit=120:loopsc"%server%"query"%service%"|find"STOPPED"iferrorlevel1 (ping 127.0.0.1 -n 1>NULset /a count +=1if %count% lss %limit% goto loop)sc"%server%"query"%service%"|find"STOPPED"iferrorlevel1 (echo"Timed out"exit /b 1)echo"Service stopped, waiting 5 seconds"timeout /t 5 /nobreak >nulecho"Setting new password"sc"%server%" config "%service%" obj=%user% password="%newPassword%"if %errorlevel% neq0exit /b %errorlevel%echo"Updated, waiting 2 seconds"timeout /t 5 /nobreak >nulecho"Starting service"sc"%server%"start"%service%"if %errorlevel% neq0exit /b %errorlevel%