DSCResources/ArcGIS_WindowsService/ArcGIS_WindowsService.psm1

$modulePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Modules'

# Import the ArcGIS Common Modules
Import-Module -Name (Join-Path -Path $modulePath `
        -ChildPath (Join-Path -Path 'ArcGIS.Common' `
            -ChildPath 'ArcGIS.Common.psm1'))

<#
    .SYNOPSIS
        Resource to start or stop a Windows Service.
    .PARAMETER Name
        Name of the Windows Service
    .PARAMETER StartupType
       Window Service startup Mode - "Automatic", "Manual", "Disabled"
    .PARAMETER BuiltInAccount
        Type of Account which is used to run the Windows Service - "LocalSystem", "LocalService", "NetworkService"
    .PARAMETER Credential
        A MSFT_Credential Object - The Account credentials used to run the service
    .PARAMETER State
        State of the Window Service - "Running", "Stopped"
#>


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}'. Message: '{1}'
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
"@

}

<#
.Synopsis
Gets a service resource
#>

function Get-TargetResource
{
    param
    (
        
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Name,

        [parameter(Mandatory = $true)]
        [System.String]
        [ValidateSet("Running", "Stopped")]
        $State = "Running"
    )

    $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 | ForEach-Object{$_.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,

        [parameter(Mandatory = $true)]
        [System.String]
        [ValidateSet("Running", "Stopped")]
        $State="Running"
    )
    
    ValidateStartupType $Name $StartupType $State

    $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($null -ne $userName -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,

        [parameter(Mandatory = $true)]
        [System.String]
        [ValidateSet("Running", "Stopped")]
        $State="Running"
    )
    
    try {
        ValidateStartupType $Name $StartupType $State

        $svc=GetServiceResource $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")
        {
            # Ensuring service is stopped
            StopService $svc 
            return
        }

        # $State is Running, so ensuring service is started
        StartService $svc    
    }
    catch {
        Write-Verbose "[WARNING] Stopping Server :- $_"
    }
}


<#
.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($null -eq $StartupType) {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 new-object management.managementobject "Win32_Service.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 = $svcWmi.Change($null,$null,$null,$null,$StartupType,$null,$null,$null)
        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") -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($null -ne $userName -and !(TestUserName $SvcWmi $userName) -and $PSCmdlet.ShouldProcess($SvcWmi.Name,$LocalizedData.SetCredentialWhatIf))
    {
        if($PSBoundParameters.ContainsKey("Credential"))
        {
            SetLogOnAsServicePolicy $userName
        }

        $ret = $SvcWmi.Change($null,$null,$null,$null,$null,$null,$userName,$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 -ErrorAction Ignore 2>&1 #
    if($null -eq $err)
    {
        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
        {

            Write-Log ($LocalizedData.ErrorStartingService -f $svc.Name,$_.Exception.Message)
            throw
        }

        Write-Log ($LocalizedData.ServiceStarted -f $svc.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($null -eq $svc)
    {
        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
                /// <summary>
                /// Used to open a policy, but not containing anything meaqningful
                /// </summary>
                [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;
                    }
                }
 
                /// <summary>
                /// LSA string
                /// </summary>
                [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));
                    }
                }
 
                /// <summary>
                /// Structure used as the last parameter for LSALookupNames
                /// </summary>
                [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
                /// <summary>
                /// Handle for LSA objects including Policy and Account
                /// </summary>
                private class LsaSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
                {
                    [DllImport("advapi32.dll")]
                    private static extern uint LsaClose(IntPtr ObjectHandle);
 
                    /// <summary>
                    /// Prevents a default instance of the LsaPolicySafeHAndle class from being created.
                    /// </summary>
                    private LsaSafeHandle(): base(true)
                    {
                    }
 
                    /// <summary>
                    /// Calls NativeMethods.CloseHandle(handle)
                    /// </summary>
                    /// <returns>the return of NativeMethods.CloseHandle(handle)</returns>
                    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
                    protected override bool ReleaseHandle()
                    {
                        long returnValue = LsaSafeHandle.LsaClose(this.handle);
                        return returnValue != 0;
                 
                    }
                }
 
                /// <summary>
                /// Handle for IntPtrs returned from Lsa calls that have to be freed with
                /// LsaFreeMemory
                /// </summary>
                private class SafeLsaMemoryHandle : SafeHandleZeroOrMinusOneIsInvalid
                {
                    [DllImport("advapi32")]
                    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
                /// <summary>
                /// Opens LSA Policy
                /// </summary>
                [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
                private static extern uint LsaOpenPolicy(
                    IntPtr SystemName,
                    ref LSA_OBJECT_ATTRIBUTES ObjectAttributes,
                    uint DesiredAccess,
                    out LsaSafeHandle PolicyHandle
                );
 
                /// <summary>
                /// Convert the name into a SID which is used in remaining calls
                /// </summary>
                [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute]
                private static extern uint LsaLookupNames2(
                    LsaSafeHandle PolicyHandle,
                    uint Flags,
                    uint Count,
                    LSA_UNICODE_STRING[] Names,
                    out SafeLsaMemoryHandle ReferencedDomains,
                    out SafeLsaMemoryHandle Sids
                );
 
                /// <summary>
                /// Opens the LSA account corresponding to the user's SID
                /// </summary>
                [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
                private static extern uint LsaOpenAccount(
                    LsaSafeHandle PolicyHandle,
                    IntPtr Sid,
                    uint Access,
                    out LsaSafeHandle AccountHandle);
 
                /// <summary>
                /// Creates an LSA account corresponding to the user's SID
                /// </summary>
                [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
                private static extern uint LsaCreateAccount(
                    LsaSafeHandle PolicyHandle,
                    IntPtr Sid,
                    uint Access,
                    out LsaSafeHandle AccountHandle);
 
                /// <summary>
                /// Gets the LSA Account access
                /// </summary>
                [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
                private static extern uint LsaGetSystemAccessAccount(
                    LsaSafeHandle AccountHandle,
                    out uint SystemAccess);
 
                /// <summary>
                /// Sets the LSA Account access
                /// </summary>
                [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
                private static extern uint LsaSetSystemAccessAccount(
                    LsaSafeHandle AccountHandle,
                    uint SystemAccess);
                #endregion interop function declarations
 
                /// <summary>
                /// Sets the Log On As A Service Policy for <paramref name="userName"/>, if not already set.
                /// </summary>
                /// <param name="userName">the user name we want to allow logging on as a service</param>
                /// <exception cref="ArgumentNullException">If the <paramref name="userName"/> is null or empty.</exception>
                /// <exception cref="InvalidOperationException">In the following cases:
                /// Failure opening the LSA Policy.
                /// The <paramref name="userName"/> 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.
                /// </exception>
                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)
        Add-Type $logOnAsServiceText -Verbose
    }

    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