data LocalizedData { # culture="en-US" ConvertFrom-StringData @" ServiceNotFound=Service '{0}' not found. CannotStartAndDisable=Cannot start and disable a service. CannotStopServiceSetToStartAutomatically=Cannot stop a service and set it to start automatically. ServiceAlreadyStarted=Service '{0}' already started, no action required. ServiceStarted=Service '{0}' started. ServiceStopped=Service '{0}' stopped. ErrorStartingService=Failure starting service '{0}'. Please check the path '{1}' provided for the service. Message: '{2}' OnlyOneParameterCanBeSpecified=Only one of the following parameters can be specified: '{0}', '{1}'. StartServiceWhatIf=Start Service ServiceAlreadyStopped=Service '{0}' already stopped, no action required. ErrorStoppingService=Failure stopping service '{0}'. Message: '{1}' ErrorRetrievingServiceInformation=Failure retrieving information for service '{0}'. Message: '{1}' ErrorSettingServiceCredential=Failure setting credentials for service '{0}'. Message: '{1}' SetCredentialWhatIf=Set Credential SetStartupTypeWhatIf=Set Start Type ErrorSettingServiceStartupType=Failure setting start type for service '{0}'. Message: '{1}' TestUserNameMismatch=User name for service '{0}' is '{1}'. It does not match '{2}. TestStartupTypeMismatch=Startup type for service '{0}' is '{1}'. It does not match '{2}'. MethodFailed=The '{0}' method of '{1}' failed with error code: '{2}'. ErrorChangingProperty=Failed to change '{0}' property. Message: '{1}' ErrorSetingLogOnAsServiceRightsForUser=Error granting '{0}' the right to log on as a service. Message: '{1}'. CannotOpenPolicyErrorMessage=Cannot open policy manager UserNameTooLongErrorMessage=User name is too long CannotLookupNamesErrorMessage=Failed to lookup user name CannotOpenAccountErrorMessage=Failed to open policy for user CannotCreateAccountAccessErrorMessage=Failed to create policy for user CannotGetAccountAccessErrorMessage=Failed to get user policy rights CannotSetAccountAccessErrorMessage=Failed to set user policy rights BinaryPathNotSpecified=Specify the path to the executable when trying to create a new service ServiceAlreadyExists=The service '{0}' to create already exists ServiceExistsSamePath=The service '{0}' to create already exists with path '{1}' ServiceNotExists=The service '{0}' does not exist. Specify the path to the executable to create a new service ErrorDeletingService=Error in deleting service '{0}' ServiceDeletedSuccessfully=Service '{0}' Deleted Successfully TryDeleteAgain=Wait for 2 milliseconds for a service to get deleted WritePropertiesIgnored=Service '{0}' already exists. Write properties such as Status, DisplayName, Description, Dependencies will be ignored for existing services. "@ } Import-LocalizedData LocalizedData -filename MSFT_ServiceResource.strings.psd1 $EscapedLocalizedData = @{} foreach( $key in $LocalizedData.Keys ) { $EscapedLocalizedData.Add($key, $LocalizedData[$key].Replace('"','""')) } $LocalizedData = $EscapedLocalizedData Import-Module "$PSScriptRoot\..\RunAsHelper.psm1" <# .Synopsis Gets a service resource #> function Get-TargetResource { param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Name ) $svc = GetServiceResource $Name $svcWmi = GetWMIService $Name return @{ Name=$svc.Name StartupType=(NormalizeStartupType $svcWmi.StartMode).ToString() BuiltInAccount=if($svcWmi.StartName -ieq "LocalSystem") {"LocalSystem"} ` elseif($svcWmi.StartName -ieq "NT Authority\NetworkService") {"NetworkService"} ` elseif($svcWmi.StartName -ieq "NT Authority\LocalService") {"LocalService"} else {$null} State=$svc.Status.ToString() Path=$svcWmi.PathName DisplayName=$svc.DisplayName Description=$svcWmi.Description Dependencies=[string[]](@() + ($svc.ServicesDependedOn | %{$_.Name})) } } <# .Synopsis Tests a service resource #> function Test-TargetResource { param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Name, [System.String] [ValidateSet("Automatic", "Manual", "Disabled")] $StartupType, [System.String] [ValidateSet("LocalSystem", "LocalService", "NetworkService")] $BuiltInAccount, [System.Management.Automation.PSCredential] [ValidateNotNull()] $Credential, [System.String] [ValidateSet("Running", "Stopped")] $State="Running", [System.String] [ValidateNotNullOrEmpty()] $DisplayName, [System.String] [ValidateNotNullOrEmpty()] $Description, [System.String] [ValidateNotNullOrEmpty()] $Path, [System.String[]] [ValidateNotNullOrEmpty()] $Dependencies, [System.String] [ValidateSet("Present", "Absent")] $Ensure="Present" ) ValidateStartupType $Name $StartupType $State $serviceExists = ServiceExists -Name $Name -Path $Path -ErrorAction SilentlyContinue if($Ensure -eq "Absent") { if($serviceExists) { return $false } return $true } if(!$serviceExists) { return $false; } $svc=GetServiceResource $Name if($PSBoundParameters.ContainsKey("StartupType") -or $PSBoundParameters.ContainsKey("BuiltInAccount") -or $PSBoundParameters.ContainsKey("Credential")) { $svcWmi = GetWMIService $Name $getUserNameAndPasswordArgs=@{} if($PSBoundParameters.ContainsKey("BuiltInAccount")) {$null=$getUserNameAndPasswordArgs.Add("BuiltInAccount",$BuiltInAccount)} if($PSBoundParameters.ContainsKey("Credential")) {$null=$getUserNameAndPasswordArgs.Add("Credential",$Credential)} $userName,$password=GetUserNameAndPassword @getUserNameAndPasswordArgs if($userName -ne $null -and !(TestUserName $SvcWmi $userName)) { write-verbose ($LocalizedData.TestUserNameMismatch -f $svcWmi.Name,$svcWmi.StartName,$userName) return $false } if($PSBoundParameters.ContainsKey("StartupType") -and !(TestStartupType $SvcWmi $StartupType)) { write-verbose ($LocalizedData.TestStartupTypeMismatch -f $svcWmi.Name,$svcWmi.StartMode,$StartupType) return $false } } return ($State -eq "Stopped" -and $svc.Status -eq "Stopped") -or ($svc.Status -eq "Running" -and $State -eq "Running") } <# .Synopsis Sets properties for a service resource #> function Set-TargetResource { [CmdletBinding(SupportsShouldProcess=$true)] param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Name, [System.String] [ValidateSet("Automatic", "Manual", "Disabled")] $StartupType, [System.String] [ValidateSet("LocalSystem", "LocalService", "NetworkService")] $BuiltInAccount, [System.Management.Automation.PSCredential] [ValidateNotNull()] $Credential, [System.String] [ValidateSet("Running", "Stopped")] $State="Running", [System.String] [ValidateNotNullOrEmpty()] $DisplayName, [System.String] [ValidateNotNullOrEmpty()] $Description, [System.String] [ValidateNotNullOrEmpty()] $Path, [System.String[]] [ValidateNotNullOrEmpty()] $Dependencies, [System.String] [ValidateSet("Present", "Absent")] $Ensure="Present" ) ValidateStartupType $Name $StartupType $State if($Ensure -eq "Absent") { $svc = GetServiceResource $Name StopService $svc DeleteService $svc.Name return } $serviceExists = ServiceExists -Name $Name -ErrorAction SilentlyContinue if($PSBoundParameters.ContainsKey("Path") -and $serviceExists) { if(CompareServicePath -Path $Path -Name $Name) { ThrowInvalidArgumentError "ServiceExistsSamePath" ($LocalizedData.ServiceExistsSamePath -f $Name, $Path) } ThrowInvalidArgumentError "ServiceAlreadyExists" ($LocalizedData.ServiceAlreadyExists -f $Name) } elseif($PSBoundParameters.ContainsKey("Path") -and !$serviceExists) { $argumentsToNewService = @{} $argumentsToNewService.Add("Name", $Name) $argumentsToNewService.Add("BinaryPathName", $Path) if($PSBoundParameters.ContainsKey("Credential")) { $argumentsToNewService.Add("Credential", $Credential) } if($PSBoundParameters.ContainsKey("StartupType")) { $argumentsToNewService.Add("StartupType", $StartupType) } if($PSBoundParameters.ContainsKey("DisplayName")) { $argumentsToNewService.Add("DisplayName", $DisplayName) } if($PSBoundParameters.ContainsKey("Description")) { $argumentsToNewService.Add("Description", $Description) } if($PSBoundParameters.ContainsKey("Dependencies")) { $argumentsToNewService.Add("DependsOn", $Dependencies) } try { New-Service @argumentsToNewService $serviceIsNew = $true } catch { Write-Log ("Error creating service `"$($argumentsToNewService["Name"])`"", $_.Exception.Message) throw $_ } } elseif(!$PSBoundParameters.ContainsKey("Path") -and !$serviceExists) { throw $LocalizedData.ServiceNotExists -f $Name } $svc=GetServiceResource $Name if(!$serviceIsNew) { Write-Verbose ($LocalizedData.WritePropertiesIgnored -f $Name) } $writeWritePropertiesArguments=@{Name=$svc.name} if($PSBoundParameters.ContainsKey("StartupType")) {$null=$writeWritePropertiesArguments.Add("StartupType",$StartupType)} if($PSBoundParameters.ContainsKey("BuiltInAccount")) {$null=$writeWritePropertiesArguments.Add("BuiltInAccount",$BuiltInAccount)} if($PSBoundParameters.ContainsKey("Credential")) {$null=$writeWritePropertiesArguments.Add("Credential",$Credential)} WriteWriteProperties @writeWritePropertiesArguments if($State -eq "Stopped") { # Ensure service is stopped StopService $svc return } # Default state of a newly created service is 'stopped'. If $State=Running, ensure service is started. if($State -eq "Running") { StartService $svc } } <# .Synopsis Validates if a service exist using the Name parameter #> function ServiceExists { param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Name, [System.String] $Path ) $service = Get-Service -Name $Name -ErrorAction SilentlyContinue if($service -ne $null) { if($Path -ne $null -and $Path -ne '' -and !(CompareServicePath -Name $Name -Path $Path)){ return $false } return $true } return $false } <# .Synopsis Compares path to the service path, if the service exists. Returns true when path is same as service path. #> function CompareServicePath { param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Path, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Name ) $servicePath = (Get-CimInstance -Class win32_service | where {$_.Name -eq $Name}).PathName $result = [string]::Compare($Path, $servicePath, [System.Globalization.CultureInfo]::CurrentUICulture) if($result -ne 0) { return $false } return $true } <# .Synopsis Validates a StartupType against the State parameter #> function ValidateStartupType { param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Name, [System.String] $StartupType, [System.String] [ValidateSet("Running", "Stopped")] $State="Running" ) if($StartupType -eq $null) {return} if($State -eq "Stopped") { if($StartupType -eq "Automatic") { # State = Stopped conflicts with Automatic or Delayed ThrowInvalidArgumentError "CannotStopServiceSetToStartAutomatically" ($LocalizedData.CannotStopServiceSetToStartAutomatically -f $Name) } } else { if($StartupType -eq "Disabled") { # State = Running conflicts with Disabled ThrowInvalidArgumentError "CannotStartAndDisable" ($LocalizedData.CannotStartAndDisable -f $Name) } } } <# .Synopsis Writes all write properties if not already correctly set, logging errors and respecting whatif #> function WriteWriteProperties { [CmdletBinding(SupportsShouldProcess=$true)] param ( [parameter(Mandatory = $true)] [ValidateNotNull()] $Name, [System.String] [ValidateSet("Automatic", "Manual", "Disabled")] $StartupType, [System.String] [ValidateSet("LocalSystem", "LocalService", "NetworkService")] $BuiltInAccount, [System.Management.Automation.PSCredential] [ValidateNotNull()] $Credential ) if(!$PSBoundParameters.ContainsKey("StartupType") -and !$PSBoundParameters.ContainsKey("BuiltInAccount") -and !$PSBoundParameters.ContainsKey("Credential")) { return } $svcWmi = GetWMIService $Name $writeCredentialPropertiesArguments=@{"SvcWmi"=$svcWmi} if($PSBoundParameters.ContainsKey("BuiltInAccount")) {$null=$writeCredentialPropertiesArguments.Add("BuiltInAccount",$BuiltInAccount)} if($PSBoundParameters.ContainsKey("Credential")) {$null=$writeCredentialPropertiesArguments.Add("Credential",$Credential)} WriteCredentialProperties @writeCredentialPropertiesArguments $writeStartupArguments=@{"SvcWmi"=$svcWmi} if($PSBoundParameters.ContainsKey("StartupType")) {$null=$writeStartupArguments.Add("StartupType",$StartupType)} WriteStartupTypeProperty @writeStartupArguments } <# .Synopsis Gets a Win32_Service object corresponding to the name #> function GetWMIService { param ( [parameter(Mandatory = $true)] [ValidateNotNull()] $Name ) try { return Get-CimInstance -ClassName Win32_Service -Filter "Name='$Name'" } catch { Write-Verbose ($LocalizedData.ErrorRetrievingServiceInformation -f $Name,$_.Exception.Message) throw } } <# .Synopsis Writes StartupType if not already correctly set, logging errors and respecting whatif #> function WriteStartupTypeProperty { [CmdletBinding(SupportsShouldProcess=$true)] param ( [parameter(Mandatory = $true)] [ValidateNotNull()] $SvcWmi, [System.String] $StartupType ) if($PSBoundParameters.ContainsKey("StartupType") -and !(TestStartupType $SvcWmi $StartupType) -and $PSCmdlet.ShouldProcess($svcWmi.Name,$LocalizedData.SetStartupTypeWhatIf)) { $ret = Invoke-CimMethod -InputObject $SvcWmi -MethodName Change -Arguments @{StartMode=$StartupType} if($ret.ReturnValue -ne 0) { $innerMessage = $LocalizedData.MethodFailed -f "Change","Win32_Service",$ret.ReturnValue $message = $LocalizedData.ErrorChangingProperty -f "StartupType",$innerMessage ThrowInvalidArgumentError "ChangeStartupTypeFailed" $message } } } <# .Synopsis Writes credential properties if not already correctly set, logging errors and respecting whatif #> function WriteCredentialProperties { [CmdletBinding(SupportsShouldProcess=$true)] param ( [parameter(Mandatory = $true)] [ValidateNotNull()] $SvcWmi, [System.String] [ValidateSet("LocalSystem", "LocalService", "NetworkService")] $BuiltInAccount, [System.Management.Automation.PSCredential] $Credential ) if(!$PSBoundParameters.ContainsKey("Credential") -and !$PSBoundParameters.ContainsKey("BuiltInAccount")) { return } if($PSBoundParameters.ContainsKey("Credential") -and $PSBoundParameters.ContainsKey("BuiltInAccount")) { ThrowInvalidArgumentError "OnlyCredentialOrBuiltInAccount" ($LocalizedData.OnlyOneParameterCanBeSpecified -f "Credential","BuiltInAccount") } $getUserNameAndPasswordArgs=@{} if($PSBoundParameters.ContainsKey("BuiltInAccount")) {$null=$getUserNameAndPasswordArgs.Add("BuiltInAccount",$BuiltInAccount)} if($PSBoundParameters.ContainsKey("Credential")) {$null=$getUserNameAndPasswordArgs.Add("Credential",$Credential)} $userName,$password=GetUserNameAndPassword @getUserNameAndPasswordArgs if($userName -ne $null -and !(TestUserName $SvcWmi $userName) -and $PSCmdlet.ShouldProcess($SvcWmi.Name,$LocalizedData.SetCredentialWhatIf)) { if($PSBoundParameters.ContainsKey("Credential")) { SetLogOnAsServicePolicy $userName } $ret = Invoke-CimMethod -InputObject $SvcWmi -MethodName Change -Arguments @{StartName=$userName;StartPassword=$password} if($ret.ReturnValue -ne 0) { $innerMessage = $LocalizedData.MethodFailed -f "Change","Win32_Service",$ret.ReturnValue $message = $LocalizedData.ErrorChangingProperty -f "Credential",$innerMessage ThrowInvalidArgumentError "ChangeCredentialFailed" $message } } } <# .Synopsis Returns true if the service's StartName matches $UserName #> function TestUserName { param ( $SvcWmi, [string] $UserName ) return (NormalizeUserName $SvcWmi.StartName) -ieq $UserName } function TestStartupType { param ( [parameter(Mandatory = $true)] [ValidateNotNull()] $SvcWmi, [System.String] $StartupType ) return (NormalizeStartupType $SvcWmi.StartMode) -ieq $StartupType } <# .Synopsis Retrieves user name and password out of the BuiltInAccount and Credential parameters #> function GetUserNameAndPassword { param ( [System.String] [ValidateSet("LocalSystem", "LocalService", "NetworkService")] $BuiltInAccount, [System.Management.Automation.PSCredential] $Credential ) if($PSBoundParameters.ContainsKey("BuiltInAccount")) { return (NormalizeUserName $BuiltInAccount.ToString()),$null } if($PSBoundParameters.ContainsKey("Credential")) { return (NormalizeUserName $Credential.UserName),$Credential.GetNetworkCredential().Password } return $null,$null } <# .Synopsis Stops a service if it is not already stopped logging the result #> function StopService { [CmdletBinding(SupportsShouldProcess=$true)] param ( [parameter(Mandatory = $true)] [ValidateNotNull()] $svc ) if($svc.Status -eq [System.ServiceProcess.ServiceControllerStatus]::Stopped) { Write-Log ($LocalizedData.ServiceAlreadyStopped -f $svc.Name) return } # Exceptions will be thrown, caught and logged by the infrastructure $err=Stop-Service $svc.Name -force 2>&1 if($err -eq $null) { Write-Log ($LocalizedData.ServiceStopped -f $svc.Name) } else { Write-Log ($LocalizedData.ErrorStoppingService -f $svc.Name,($err | Out-String)) throw $err } } <# .Synopsis Starts a service if it is not already started logging the result #> function StartService { [CmdletBinding(SupportsShouldProcess=$true)] param ( [parameter(Mandatory = $true)] [ValidateNotNull()] $svc ) if($svc.Status -eq [System.ServiceProcess.ServiceControllerStatus]::Running) { Write-Log ($LocalizedData.ServiceAlreadyStarted -f $svc.Name) return } if($PSCmdlet.ShouldProcess($svc.Name,$LocalizedData.StartServiceWhatIf)) { try { $svc.Start() $twoSeconds = New-Object timespan 20000000 $svc.WaitForStatus("Running",$twoSeconds) } catch { $servicePath = (Get-CimInstance -Class win32_service | where {$_.Name -eq $Name}).PathName $message = $LocalizedData.ErrorStartingService -f $svc.Name,$servicePath,$_.Exception.Message ThrowInvalidArgumentError "ErrorStartingService" $message } Write-Log ($LocalizedData.ServiceStarted -f $svc.Name) } } <# .Synopsis Deletes a service #> function DeleteService { [CmdletBinding(SupportsShouldProcess = $true)] param ( [parameter(Mandatory = $true)] [ValidateNotNull()] $Name ) $err = & "sc.exe" "delete" "$Name" for($i = 1; $i -lt 1000; $i++) { if(!(ServiceExists -Name $Name)) { $serviceDeletedSuccessfully = $true break } #try again after 2 millisecs if the service is not deleted. Write-Verbose ($LocalizedData.TryDeleteAgain) Start-Sleep .002 } if(!$serviceDeletedSuccessfully) { Write-Log ($LocalizedData.ErrorDeletingService -f $Name) throw $LocalizedData.ErrorDeletingService -f $Name } else { Write-Log ($LocalizedData.ServiceDeletedSuccessfully -f $Name) } } function NormalizeStartupType([string]$StartupType) { if ($StartupType -ieq 'Auto') {return "Automatic"} return $StartupType } function NormalizeUserName([string]$UserName) { if ($UserName -ieq 'NetworkService') {return "NT Authority\NetworkService"} if ($UserName -ieq 'LocalService') {return "NT Authority\LocalService"} if ($UserName -ieq 'LocalSystem') {return ".\LocalSystem"} if ($UserName.IndexOf("\") -eq -1) { return ".\" + $userName } return $UserName } <# .Synopsis Throws an argument error #> function ThrowInvalidArgumentError { [CmdletBinding()] param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $errorId, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $errorMessage ) $errorCategory=[System.Management.Automation.ErrorCategory]::InvalidArgument $exception = New-Object System.ArgumentException $errorMessage; $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null throw $errorRecord } <# .Synopsis Gets a service corresponding to a name, throwing an error if not found #> function GetServiceResource { param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Name ) $svc=Get-Service $name -ErrorAction Ignore if($svc -eq $null) { ThrowInvalidArgumentError "ServiceNotFound" ($LocalizedData.ServiceNotFound -f $Name) } return $svc } <# .Synopsis Grants log on as service right to the given user #> function SetLogOnAsServicePolicy([string]$userName) { $logOnAsServiceText=@" namespace LogOnAsServiceHelper { using Microsoft.Win32.SafeHandles; using System; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Security; public class NativeMethods { #region constants // from ntlsa.h private const int POLICY_LOOKUP_NAMES = 0x00000800; private const int POLICY_CREATE_ACCOUNT = 0x00000010; private const uint ACCOUNT_ADJUST_SYSTEM_ACCESS = 0x00000008; private const uint ACCOUNT_VIEW = 0x00000001; private const uint SECURITY_ACCESS_SERVICE_LOGON = 0x00000010; // from LsaUtils.h private const uint STATUS_OBJECT_NAME_NOT_FOUND = 0xC0000034; // from lmcons.h private const int UNLEN = 256; private const int DNLEN = 15; // Extra characteres for "\","@" etc. private const int EXTRA_LENGTH = 3; #endregion constants #region interop structures /// /// Used to open a policy, but not containing anything meaqningful /// [StructLayout(LayoutKind.Sequential)] private struct LSA_OBJECT_ATTRIBUTES { public UInt32 Length; public IntPtr RootDirectory; public IntPtr ObjectName; public UInt32 Attributes; public IntPtr SecurityDescriptor; public IntPtr SecurityQualityOfService; public void Initialize() { this.Length = 0; this.RootDirectory = IntPtr.Zero; this.ObjectName = IntPtr.Zero; this.Attributes = 0; this.SecurityDescriptor = IntPtr.Zero; this.SecurityQualityOfService = IntPtr.Zero; } } /// /// LSA string /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct LSA_UNICODE_STRING { internal ushort Length; internal ushort MaximumLength; [MarshalAs(UnmanagedType.LPWStr)] internal string Buffer; internal void Set(string src) { this.Buffer = src; this.Length = (ushort)(src.Length * sizeof(char)); this.MaximumLength = (ushort)(this.Length + sizeof(char)); } } /// /// Structure used as the last parameter for LSALookupNames /// [StructLayout(LayoutKind.Sequential)] private struct LSA_TRANSLATED_SID2 { public uint Use; public IntPtr SID; public int DomainIndex; public uint Flags; }; #endregion interop structures #region safe handles /// /// Handle for LSA objects including Policy and Account /// private class LsaSafeHandle : SafeHandleZeroOrMinusOneIsInvalid { #if CORECLR [DllImport("api-ms-win-security-lsapolicy-l1-1-0.dll")] #else [DllImport("advapi32.dll")] #endif private static extern uint LsaClose(IntPtr ObjectHandle); /// /// Prevents a default instance of the LsaPolicySafeHAndle class from being created. /// private LsaSafeHandle(): base(true) { } /// /// Calls NativeMethods.CloseHandle(handle) /// /// the return of NativeMethods.CloseHandle(handle) #if !CORECLR [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] #endif protected override bool ReleaseHandle() { long returnValue = LsaSafeHandle.LsaClose(this.handle); return returnValue != 0; } } /// /// Handle for IntPtrs returned from Lsa calls that have to be freed with /// LsaFreeMemory /// private class SafeLsaMemoryHandle : SafeHandleZeroOrMinusOneIsInvalid { #if CORECLR [DllImport("api-ms-win-security-lsapolicy-l1-1-0.dll")] #else [DllImport("advapi32")] #endif internal static extern int LsaFreeMemory(IntPtr Buffer); private SafeLsaMemoryHandle() : base(true) { } private SafeLsaMemoryHandle(IntPtr handle) : base(true) { SetHandle(handle); } private static SafeLsaMemoryHandle InvalidHandle { get { return new SafeLsaMemoryHandle(IntPtr.Zero); } } override protected bool ReleaseHandle() { return SafeLsaMemoryHandle.LsaFreeMemory(handle) == 0; } internal IntPtr Memory { get { return this.handle; } } } #endregion safe handles #region interop function declarations /// /// Opens LSA Policy /// #if CORECLR [DllImport("api-ms-win-security-lsapolicy-l1-1-0.dll", SetLastError = true, PreserveSig = true)] #else [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)] #endif private static extern uint LsaOpenPolicy( IntPtr SystemName, ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, uint DesiredAccess, out LsaSafeHandle PolicyHandle ); /// /// Convert the name into a SID which is used in remaining calls /// #if CORECLR [DllImport("api-ms-win-security-lsapolicy-l1-1-0.dll", CharSet = CharSet.Unicode, SetLastError = true)] #else [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute] #endif private static extern uint LsaLookupNames2( LsaSafeHandle PolicyHandle, uint Flags, uint Count, LSA_UNICODE_STRING[] Names, out SafeLsaMemoryHandle ReferencedDomains, out SafeLsaMemoryHandle Sids ); /// /// Opens the LSA account corresponding to the user's SID /// #if CORECLR [DllImport("advapi32legacy.dll", SetLastError = true, PreserveSig = true)] #else [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)] #endif private static extern uint LsaOpenAccount( LsaSafeHandle PolicyHandle, IntPtr Sid, uint Access, out LsaSafeHandle AccountHandle); /// /// Creates an LSA account corresponding to the user's SID /// #if CORECLR [DllImport("advapi32legacy.dll", SetLastError = true, PreserveSig = true)] #else [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)] #endif private static extern uint LsaCreateAccount( LsaSafeHandle PolicyHandle, IntPtr Sid, uint Access, out LsaSafeHandle AccountHandle); /// /// Gets the LSA Account access /// #if CORECLR [DllImport("advapi32legacy.dll", SetLastError = true, PreserveSig = true)] #else [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)] #endif private static extern uint LsaGetSystemAccessAccount( LsaSafeHandle AccountHandle, out uint SystemAccess); /// /// Sets the LSA Account access /// #if CORECLR [DllImport("advapi32legacy.dll", SetLastError = true, PreserveSig = true)] #else [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)] #endif private static extern uint LsaSetSystemAccessAccount( LsaSafeHandle AccountHandle, uint SystemAccess); #endregion interop function declarations /// /// Sets the Log On As A Service Policy for , if not already set. /// /// the user name we want to allow logging on as a service /// If the is null or empty. /// In the following cases: /// Failure opening the LSA Policy. /// The is too large. /// Failure looking up the user name. /// Failure opening LSA account (other than account not found). /// Failure creating LSA account. /// Failure getting LSA account policy access. /// Failure setting LSA account policy access. /// public static void SetLogOnAsServicePolicy(string userName) { if (String.IsNullOrEmpty(userName)) { throw new ArgumentNullException("userName"); } LSA_OBJECT_ATTRIBUTES objectAttributes = new LSA_OBJECT_ATTRIBUTES(); objectAttributes.Initialize(); // All handles are delcared in advance so they can be closed on finally LsaSafeHandle policyHandle = null; SafeLsaMemoryHandle referencedDomains = null; SafeLsaMemoryHandle sids = null; LsaSafeHandle accountHandle = null; try { uint status = LsaOpenPolicy( IntPtr.Zero, ref objectAttributes, POLICY_LOOKUP_NAMES | POLICY_CREATE_ACCOUNT, out policyHandle); if (status != 0) { throw new InvalidOperationException(@"CannotOpenPolicyErrorMessage"); } // Unicode strings have a maximum length of 32KB. We don't want to create // LSA strings with more than that. User lengths are much smaller so this check // ensures userName's length is useful if (userName.Length > UNLEN + DNLEN + EXTRA_LENGTH) { throw new InvalidOperationException(@"UserNameTooLongErrorMessage"); } LSA_UNICODE_STRING lsaUserName = new LSA_UNICODE_STRING(); lsaUserName.Set(userName); LSA_UNICODE_STRING[] names = new LSA_UNICODE_STRING[1]; names[0].Set(userName); status = LsaLookupNames2( policyHandle, 0, 1, new LSA_UNICODE_STRING[] { lsaUserName }, out referencedDomains, out sids); if (status != 0) { throw new InvalidOperationException(@"CannotLookupNamesErrorMessage"); } LSA_TRANSLATED_SID2 sid = (LSA_TRANSLATED_SID2)Marshal.PtrToStructure(sids.Memory, typeof(LSA_TRANSLATED_SID2)); status = LsaOpenAccount(policyHandle, sid.SID, ACCOUNT_VIEW | ACCOUNT_ADJUST_SYSTEM_ACCESS, out accountHandle); uint currentAccess = 0; if (status == 0) { status = LsaGetSystemAccessAccount(accountHandle, out currentAccess); if (status != 0) { throw new InvalidOperationException(@"CannotGetAccountAccessErrorMessage"); } } else if (status == STATUS_OBJECT_NAME_NOT_FOUND) { status = LsaCreateAccount( policyHandle, sid.SID, ACCOUNT_ADJUST_SYSTEM_ACCESS, out accountHandle); if (status != 0) { throw new InvalidOperationException(@"CannotCreateAccountAccessErrorMessage"); } } else { throw new InvalidOperationException(@"CannotOpenAccountErrorMessage"); } if ((currentAccess & SECURITY_ACCESS_SERVICE_LOGON) == 0) { status = LsaSetSystemAccessAccount( accountHandle, currentAccess | SECURITY_ACCESS_SERVICE_LOGON); if (status != 0) { throw new InvalidOperationException(@"CannotSetAccountAccessErrorMessage"); } } } finally { if (policyHandle != null) { policyHandle.Close(); } if (referencedDomains != null) { referencedDomains.Close(); } if (sids != null) { sids.Close(); } if (accountHandle != null) { accountHandle.Close(); } } } } } "@ try { $existingType=[LogOnAsServiceHelper.NativeMethods] } catch { $logOnAsServiceText=$logOnAsServiceText.Replace("CannotOpenPolicyErrorMessage",$LocalizedData.CannotOpenPolicyErrorMessage) $logOnAsServiceText=$logOnAsServiceText.Replace("UserNameTooLongErrorMessage",$LocalizedData.UserNameTooLongErrorMessage) $logOnAsServiceText=$logOnAsServiceText.Replace("CannotLookupNamesErrorMessage",$LocalizedData.CannotLookupNamesErrorMessage) $logOnAsServiceText=$logOnAsServiceText.Replace("CannotOpenAccountErrorMessage",$LocalizedData.CannotOpenAccountErrorMessage) $logOnAsServiceText=$logOnAsServiceText.Replace("CannotCreateAccountAccessErrorMessage",$LocalizedData.CannotCreateAccountAccessErrorMessage) $logOnAsServiceText=$logOnAsServiceText.Replace("CannotGetAccountAccessErrorMessage",$LocalizedData.CannotGetAccountAccessErrorMessage) $logOnAsServiceText=$logOnAsServiceText.Replace("CannotSetAccountAccessErrorMessage",$LocalizedData.CannotSetAccountAccessErrorMessage) if(IsNanoServer) { $logOnAsServiceText = "#define CORECLR`n" + $logOnAsServiceText } $null = Add-Type $logOnAsServiceText -PassThru -Debug:$false } if($userName.StartsWith(".\")) { $userName = $userName.Substring(2) } try { [LogOnAsServiceHelper.NativeMethods]::SetLogOnAsServicePolicy($userName) } catch { $message = $LocalizedData.ErrorSetingLogOnAsServiceRightsForUser -f $userName,$_.Exception.Message ThrowInvalidArgumentError "ErrorSetingLogOnAsServiceRightsForUser" $message } } function Write-Log { [CmdletBinding(SupportsShouldProcess=$true)] param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Message ) if ($PSCmdlet.ShouldProcess($Message, $null, $null)) { Write-Verbose $Message } } Export-ModuleMember -function Get-TargetResource, Set-TargetResource, Test-TargetResource