I\'m trying to use powershell to configure the account credentials, but I need to grant the account \"Log on as a service\" right in order for it to work. How can I do this
There is a Windows API that provides access to the Local Security Policy: AdvAPI32.dll
and the Lsa*
methods.
You can take advantage of PowerShell's ability to add .NET types to the current session, and .NET's P/Invoke to call Windows APIs, in order to grant a user the 'Log on as a service' permission from PowerShell.
As a minimal example*, you first need to add some new .NET types to the current PowerShell session to work with the Windows API:
Add-Type @'
using System;
using System.Runtime.InteropServices;
public enum LSA_AccessPolicy : long
{
// Other values omitted for clarity
POLICY_ALL_ACCESS = 0x00001FFFL
}
[StructLayout(LayoutKind.Sequential)]
public struct LSA_UNICODE_STRING
{
public UInt16 Length;
public UInt16 MaximumLength;
public IntPtr Buffer;
}
[StructLayout(LayoutKind.Sequential)]
public struct LSA_OBJECT_ATTRIBUTES
{
public UInt32 Length;
public IntPtr RootDirectory;
public LSA_UNICODE_STRING ObjectName;
public UInt32 Attributes;
public IntPtr SecurityDescriptor;
public IntPtr SecurityQualityOfService;
}
public static partial class AdvAPI32 {
[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
public static extern uint LsaOpenPolicy(
ref LSA_UNICODE_STRING SystemName,
ref LSA_OBJECT_ATTRIBUTES ObjectAttributes,
uint DesiredAccess,
out IntPtr PolicyHandle);
[DllImport("advapi32.dll")]
public static extern Int32 LsaClose(IntPtr ObjectHandle);
[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
public static extern uint LsaAddAccountRights(
IntPtr PolicyHandle,
byte[] AccountSid,
LSA_UNICODE_STRING[] UserRights,
uint CountOfRights);
}
'@
Then, you need to open a handle to the local security policy:
function Get-LsaPolicyHandle() {
$system = New-Object LSA_UNICODE_STRING
$attrib = New-Object LSA_OBJECT_ATTRIBUTES -Property @{
Length = 0
RootDirectory = [System.IntPtr]::Zero
Attributes = 0
SecurityDescriptor = [System.IntPtr]::Zero
SecurityQualityOfService = [System.IntPtr]::Zero
};
$handle = [System.IntPtr]::Zero
$hr = [AdvAPI32]::LsaOpenPolicy([ref] $system, [ref]$attrib, [LSA_AccessPolicy]::POLICY_ALL_ACCESS, [ref]$handle)
if (($hr -ne 0) -or ($handle -eq [System.IntPtr]::Zero)) {
Write-Error "Failed to open Local Security Authority policy. Error code: $hr"
} else {
$handle
}
}
To add a new right, you first need to create a new one:
function New-Right([string]$rightName){
$unicodeCharSize = 2
New-Object LSA_UNICODE_STRING -Property @{
Buffer = [System.Runtime.InteropServices.Marshal]::StringToHGlobalUni($rightName)
Length = $rightName.Length * $unicodeCharSize
MaximumLength = ($rightName.Length + 1) * $unicodeCharSize
}
}
And then you can add that right to the security policy:
function Grant-Rights([System.IntPtr]$policyHandle, [byte[]]$sid, [LSA_UNICODE_STRING[]]$rights) {
$result = [AdvAPI32]::LsaAddAccountRights($policyHandle, $sid, $rights, 1)
if ($result -ne 0) {
Write-Error "Failed to grant right. Error code $result"
}
}
So putting it all together, you can then consume those functions:
function Grant-LogonAsServiceRight([byte[]]$sid) {
$logonAsServiceRightName = "SeServiceLogonRight"
try {
$policy = Get-LsaPolicyHandle
$right = New-Right $logonAsServiceRightName
Grant-Rights $policy $sid @($right)
}
finally {
if($null -ne $policy){
[AdvAPI32]::LsaClose($policy) | Out-Null
}
}
}
There are other methods in AdvAPI32
to enumerate a user's account rights, remove a right etc.
--
*I followed the code in the Azure DevOps Pipeline Agent repo to figure out how to work with the Local Security Policy methods in AdvAPI32
.
This is not pure PowerShell but at least you do not need a third party tool.
Everything is already on your computer and works from the command line.
#Requires -RunAsAdministrator
#The SID you want to add
$AccountSid = 'S-1-5-21-1234567890-1234567890-123456789-500'
$ExportFile = 'c:\temp\CurrentConfig.inf'
$SecDb = 'c:\temp\secedt.sdb'
$ImportFile = 'c:\temp\NewConfig.inf'
#Export the current configuration
secedit /export /cfg $ExportFile
#Find the current list of SIDs having already this right
$CurrentServiceLogonRight = Get-Content -Path $ExportFile |
Where-Object -FilterScript {$PSItem -match 'SeServiceLogonRight'}
#Create a new configuration file and add the new SID
$FileContent = @'
[Unicode]
Unicode=yes
[System Access]
[Event Audit]
[Registry Values]
[Version]
signature="$CHICAGO$"
Revision=1
[Profile Description]
Description=GrantLogOnAsAService security template
[Privilege Rights]
{0}*{1}
'@ -f $(
if($CurrentServiceLogonRight){"$CurrentServiceLogonRight,"}
else{'SeServiceLogonRight = '}
), $AccountSid
Set-Content -Path $ImportFile -Value $FileContent
#Import the new configuration
secedit /import /db $SecDb /cfg $ImportFile
secedit /configure /db $SecDb
Solution without importing the whole db
function setSecurityPolicy {
Param
(
[Parameter(Mandatory=$true, Position=0)]
[string] $username,
[Parameter(Mandatory=$true, Position=1)]
[string] $securityField
)
$sid;
if($username -like "*\*"){
$user = $username.split('\')
$domain=$user[0]
$usernametemp=$user[1]
$sid=(get-wmiobject Win32_useraccount -filter "name='$usernametemp' and Domain='$domain'").SID
} else {
$sid=(get-wmiobject Win32_useraccount -filter "name='$username' and Domain='$($env:COMPUTERNAME)'").SID
}
if(-not($sid)){
try{
$sid= (Get-Localgroup "$username").SID.VALUE
} catch{
}
}
if(-not($sid)) {
$Host.UI.WriteErrorLine("setSecurityPolicy error : Account $username not found!")
exit 1
}
$tmp = [System.IO.Path]::GetTempFileName()
secedit.exe /export /cfg "$tmp" | Out-Null
$currentSetting = Select-String -Pattern "$securityField = (.*)" -path $tmp | select -Expand Matches | % { $_.Groups[1].Value }
remove-item $tmp -Force
if($currentSetting -notlike "*$sid*" ){
Write-Host "Modify Setting ""$securityField"""
if( [string]::IsNullOrEmpty($currentSetting) ) {
$currentSetting = "*$sid"
} else {
$currentSetting = "*$sid,$currentSetting"
}
$outfile = @"
[Unicode]
Unicode=yes
[Version]
signature="`$CHICAGO`$"
Revision=1
[Privilege Rights]
$securityField = $currentSetting
"@
$tmp2 = [System.IO.Path]::GetTempFileName()
Write-Host "Import new settings to Local Security Policy"
$outfile | Set-Content -Path $tmp2 -Encoding Unicode -Force
try {
secedit.exe /configure /db "secedit.sdb" /cfg "$tmp2" /areas USER_RIGHTS
} finally {
remove-item $tmp2 -Force
}
}
}
PowerShell doesn't have any native means of doing this, which means you'd probably be looking at either WMI or ADSI - you're more likely to find examples in VBScript, which has been around longer, although personally I don't think I've ever figured out how to programmatically assign user rights. Doesn't mean it can't be done, though, but you'll probably be looking outside the realm of PowerShell specifically.
This is how I solved it:
Based on: this article
You can download Carbon from here
First import Carbon module as follows:
Import-Module -Name $Path_To_Carbon -Global -Prefix CA
[array]$UserPrivileges = Get-CAPrivileges -Identity $UserName;
[bool]$LogOnAsAServiceprivilegeFound = $false;
if ($UserPrivileges.Length > 0)
{
if ($UserPrivileges -contains "SeServiceLogonRight")
{
$LogOnAsAServiceprivilegeFound = $true;
}
}
if ($LogOnAsAServiceprivilegeFound -eq $false)
{
Grant-CAPrivilege -Identity $UserName "SeServiceLogonRight"
}
Here's a link that you could also do within PS: original | archived.
The problem is that there aren't really any public APIs for managing these settings, so you're a bit stuck using command-line tools provided in ResKits.