DSCResources/MSFT_xProcessResource/MSFT_xProcessResource.psm1

data LocalizedData
{
    # culture="en-US"
    ConvertFrom-StringData @'
FileNotFound=File not found in the environment path.
AbsolutePathOrFileName=Absolute path or file name expected.
InvalidArgument=Invalid argument: '{0}' with value: '{1}'.
InvalidArgumentAndMessage={0} {1}
ProcessStarted=Process matching path '{0}' started
ProcessesStopped=Proceses matching path '{0}' with Ids '({1})' stopped.
ProcessAlreadyStarted=Process matching path '{0}' found running and no action required.
ProcessAlreadyStopped=Process matching path '{0}' not found running and no action required.
ErrorStopping=Failure stopping processes matching path '{0}' with IDs '({1})'. Message: {2}.
ErrorStarting=Failure starting process matching path '{0}'. Message: {1}.
StartingProcessWhatif=Start-Process
ProcessNotFound=Process matching path '{0}' not found
PathShouldBeAbsolute="The path should be absolute"
PathShouldExist="The path should exist"
ParameterShouldNotBeSpecified="Parameter {0} should not be specified."
FailureWaitingForProcessesToStart="Failed to wait for processes to start"
FailureWaitingForProcessesToStop="Failed to wait for processes to stop"
'@

}

# Commented-out until more languages are supported
# Import-LocalizedData LocalizedData -filename MSFT_xProcessResource.strings.psd1

function ExtractArguments($functionBoundParameters,[string[]]$argumentNames,[string[]]$newArgumentNames)
{
    $returnValue=@{}
    for($i=0;$i -lt $argumentNames.Count;$i++)
    {
        $argumentName=$argumentNames[$i]

        if($newArgumentNames -eq $null)
        {
            $newArgumentName=$argumentName
        }
        else
        {
            $newArgumentName=$newArgumentNames[$i]
        }

        if($functionBoundParameters.ContainsKey($argumentName))
        {
            $null=$returnValue.Add($newArgumentName,$functionBoundParameters[$argumentName])
        }
    }

    return $returnValue
}

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

        [parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [System.String]
        $Arguments,

        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCredential]
        $Credential
    )

    $Path=(ResolvePath $Path)
    $PSBoundParameters["Path"] = $Path
    $getArguments = ExtractArguments $PSBoundParameters ("Path","Arguments","Credential")
    $processes = @(GetWin32_Process @getArguments)

    if($processes.Count -eq 0)
    {
        return @{
            Path=$Path
            Arguments=$Arguments
            Ensure='Absent'
        }
    }

    foreach($process in $processes)
    {
        # in case the process was killed between GetWin32_Process and this point, we should
        # ignore errors which will generate empty entries in the return
        $gpsProcess = (get-process -id $process.ProcessId -ErrorAction Ignore)

        @{
            Path=$process.Path
            Arguments=(GetProcessArgumentsFromCommandLine $process.CommandLine)
            PagedMemorySize=$gpsProcess.PagedMemorySize64
            NonPagedMemorySize=$gpsProcess.NonpagedSystemMemorySize64
            VirtualMemorySize=$gpsProcess.VirtualMemorySize64
            HandleCount=$gpsProcess.HandleCount
            Ensure='Present'
            ProcessId=$process.ProcessId
        }
    }
}


function Set-TargetResource
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Path,

        [parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [System.String]
        $Arguments,

        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCredential]
        $Credential,

        [System.String]
        [ValidateSet("Present", "Absent")]
        $Ensure="Present",

        [System.String]
        $StandardOutputPath,

        [System.String]
        $StandardErrorPath,

        [System.String]
        $StandardInputPath,

        [System.String]
        $WorkingDirectory
    )

    $Path=ResolvePath $Path
    $PSBoundParameters["Path"] = $Path
    $getArguments = ExtractArguments $PSBoundParameters ("Path","Arguments","Credential")
    $processes = @(GetWin32_Process @getArguments)

    if($Ensure -eq 'Absent')
    {
        "StandardOutputPath","StandardErrorPath","StandardInputPath","WorkingDirectory" | AssertParameterIsNotSpecified $PSBoundParameters

        if ($processes.Count -gt 0)
        {
           $processIds=$processes.ProcessId

           $err=Stop-Process -Id $processIds -force 2>&1

           if($err -eq $null)
           {
               Write-Log ($LocalizedData.ProcessesStopped -f $Path,($processIds -join ","))
           }
           else
           {
               Write-Log ($LocalizedData.ErrorStopping -f $Path,($processIds -join ","),($err | out-string))
               throw $err
           }

           # Before returning from Set-TargetResource we have to ensure a subsequent Test-TargetResource is going to work
           if (!(WaitForProcessCount @getArguments -waitCount 0))
           {
                $message = $LocalizedData.ErrorStopping -f $Path,($processIds -join ","),$LocalizedData.FailureWaitingForProcessesToStop
                Write-Log $message
                ThrowInvalidArgumentError "FailureWaitingForProcessesToStop" $message
           }
        }
        else
        {
            Write-Log ($LocalizedData.ProcessAlreadyStopped -f $Path)
        }
    }
    else
    {
        "StandardInputPath","WorkingDirectory" |  AssertAbsolutePath $PSBoundParameters -Exist
        "StandardOutputPath","StandardErrorPath" | AssertAbsolutePath $PSBoundParameters

        if ($processes.Count -eq 0)
        {
            $startArguments = ExtractArguments $PSBoundParameters `
                 ("Path",     "Arguments",    "Credential", "StandardOutputPath",     "StandardErrorPath",     "StandardInputPath", "WorkingDirectory") `
                 ("FilePath", "ArgumentList", "Credential",  "RedirectStandardOutput", "RedirectStandardError", "RedirectStandardInput", "WorkingDirectory")

            if([string]::IsNullOrEmpty($Arguments))
            {
                $null=$startArguments.Remove("ArgumentList")
            }

            if($PSCmdlet.ShouldProcess($Path,$LocalizedData.StartingProcessWhatif))
            {
                if($PSBoundParameters.ContainsKey("Credential"))
                {
                    $argumentError = $false
                    try
                    {
                        if($PSBoundParameters.ContainsKey("StandardOutputPath") -or $PSBoundParameters.ContainsKey("StandardInputPath") -or $PSBoundParameters.ContainsKey("WorkingDirectory"))
                        {
                            $argumentError = $true
                            $errorMessage = "Can't specify StandardOutptPath, StandardInputPath or WorkingDirectory when trying to run a process under a user context"
                            throw $errorMessage
                        }
                        else
                        {
                            CallPInvoke
                            [Source.NativeMethods]::CreateProcessAsUser(("$Path "+$Arguments), $Credential.GetNetworkCredential().Domain, $Credential.GetNetworkCredential().UserName, $Credential.GetNetworkCredential().Password)
                        }
                    }
                    catch
                    {
                        $exception = New-Object System.ArgumentException $_;
                        if($argumentError)
                        {
                            $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument
                            $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception,"Invalid combination of arguments", $errorCategory, $null
                        }
                        else
                        {
                            $errorCategory = [System.Management.Automation.ErrorCategory]::OperationStopped
                            $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, "Win32Exception", $errorCategory, $null
                        }
                        $err = $errorRecord
                    }

                }
                else
                {
                    $err=Start-Process @startArguments 2>&1
                }
                if($err -eq $null)
                {
                    Write-Log ($LocalizedData.ProcessStarted -f $Path)
                }
                else
                {
                    Write-Log ($LocalizedData.ErrorStarting -f $Path,($err | Out-String))
                    throw $err
                }

                # Before returning from Set-TargetResource we have to ensure a subsequent Test-TargetResource is going to work
                if (!(WaitForProcessCount @getArguments -waitCount 1))
                {
                    $message = $LocalizedData.ErrorStarting -f $Path,$LocalizedData.FailureWaitingForProcessesToStart
                    Write-Log $message
                    ThrowInvalidArgumentError "FailureWaitingForProcessesToStart" $message
                }
            }
        }
        else
        {
            Write-Log ($LocalizedData.ProcessAlreadyStarted -f $Path)
        }
    }
}

function Test-TargetResource
{
    [OutputType([System.Boolean])]
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Path,

        [parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [System.String]
        $Arguments,

        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCredential]
        $Credential,

        [System.String]
        [ValidateSet("Present", "Absent")]
        $Ensure="Present",

        [System.String]
        $StandardOutputPath,

        [System.String]
        $StandardErrorPath,

        [System.String]
        $StandardInputPath,

        [System.String]
        $WorkingDirectory
    )

    $Path=ResolvePath $Path
    $PSBoundParameters["Path"] = $Path
    $getArguments = ExtractArguments $PSBoundParameters ("Path","Arguments","Credential")
    $processes = @(GetWin32_Process @getArguments)


    if($Ensure -eq 'Absent')
    {
        return ($processes.Count -eq 0)
    }
    else
    {
        return ($processes.Count -gt 0)
    }
}

function GetWin32ProcessOwner
{
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateNotNull()]
        $process
    )

    # if the process was killed by the time this is called, GetOwner
    # will throw a WMIMethodException "Not found"
    try
    {
        $owner = $process.GetOwner()
    }
    catch
    {
    }

    if($owner.Domain -ne $null)
    {
        return $owner.Domain + "\" + $owner.User
    }
    else
    {
        return $owner.User
    }
}

function WaitForProcessCount
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Path,

        [System.String]
        $Arguments,

        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCredential]
        $Credential,

        [parameter(Mandatory=$true)]
        $waitCount
    )

    $start = [DateTime]::Now
    do
    {
        $getArguments = ExtractArguments $PSBoundParameters ("Path","Arguments","Credential")
        $value = @(GetWin32_Process @getArguments).Count -eq $waitCount
    } while(!$value -and ([DateTime]::Now - $start).TotalMilliseconds -lt 2000)

    return $value
}

function GetWin32_Process
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    param
    (

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Path,

        [System.String]
        $Arguments,

        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCredential]
        $Credential,

        $useWmiObjectCount=8
    )



    $fileName = [io.path]::GetFileNameWithoutExtension($Path)

    $gpsProcesses = @(get-process -Name $fileName -ErrorAction SilentlyContinue)

    if($gpsProcesses.Count -ge $useWmiObjectCount)
    {
        # if there are many processes it is faster to perform a Get-WmiObject
        # in order to get Win32_Process objects for all processes
        Write-Verbose "When gpsprocess.count is greater than usewmiobjectcount"
        $Path=WQLEscape $Path
        $filter = "ExecutablePath = '$Path'"
        $processes = Get-WmiObject Win32_Process -Filter $filter
    }
    else
    {
        # if there are few processes, building a Win32_Process for
        # each matching result of get-process is faster
        $processes = foreach($gpsProcess in $gpsProcesses)
        {
            if(!($gpsProcess.Path -ieq $Path))
            {
                continue
            }

            try
            {
                Write-Verbose "in process handle, $($gpsProcess.Id)"
                [wmi]"Win32_Process.Handle='$($gpsProcess.Id)'"
            }
            catch
            {
                #ignore if could not retrieve process
            }
        }
    }

    if($PSBoundParameters.ContainsKey('Credential'))
    {
        # Since there are credentials we need to call the GetOwner method in each process to search for matches
        $processes = $processes | where { (GetWin32ProcessOwner $_) -eq $Credential.UserName }

    }

    if($Arguments -eq $null) {$Arguments = ""}
    $processes = $processes | where { (GetProcessArgumentsFromCommandLine $_.CommandLine) -eq $Arguments }

    return $processes
}

<#
.Synopsis
   Strips the Arguments part of a commandLine. In "c:\temp\a.exe X Y Z" the Arguments part is "X Y Z".
#>

function GetProcessArgumentsFromCommandLine
{
    param
    (
        [System.String]
        $commandLine
    )

    if($commandLine -eq $null)
    {
        return ""
    }

    $commandLine=$commandLine.Trim()

    if($commandLine.Length -eq 0)
    {
        return ""
    }

    if($commandLine[0] -eq '"')
    {
        $charToLookfor=[char]'"'
    }
    else
    {
        $charToLookfor=[char]' '
    }

    $endofCommand=$commandLine.IndexOf($charToLookfor ,1)
    if($endofCommand -eq -1)
    {
        return ""
    }

    return $commandLine.Substring($endofCommand+1).Trim()
}

<#
.Synopsis
   Escapes a string to be used in a WQL filter as the one passed to get-wmiobject
#>

function WQLEscape
{
    param
    (

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $query
    )

    return $query.Replace("\","\\").Replace('"','\"').Replace("'","\'")
}

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
}

function ResolvePath
{
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Path
    )

    $Path = [Environment]::ExpandEnvironmentVariables($Path)

    if(IsRootedPath $Path)
    {
        if(!(Test-Path $Path -PathType Leaf))
        {
            ThrowInvalidArgumentError "CannotFindRootedPath" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $LocalizedData.FileNotFound)
        }

        return $Path
    }

    if([string]::IsNullOrEmpty($env:Path))
    {
        ThrowInvalidArgumentError "EmptyEnvironmentPath" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $LocalizedData.FileNotFound)
    }

    # This will block relative paths. The statement is only true id $Path contains a plain file name.
    # Checking a relative path against segments of the $env:Path does not make sense
    if((Split-Path $Path -Leaf) -ne $Path)
    {
        ThrowInvalidArgumentError "NotAbsolutePathOrFileName" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $LocalizedData.AbsolutePathOrFileName)
    }

    foreach($rawSegment in $env:Path.Split(";"))
    {
        $segment = [Environment]::ExpandEnvironmentVariables($rawSegment)

        # if an exception causes $segmentedRooted not to be set, we will consider it $false
        $segmentRooted = $false
        try
        {
            # If the whole path passed through [IO.Path]::IsPathRooted with no exceptions, it does not have
            # invalid characters, so segment has no invalid characters and will not throw as well
            $segmentRooted=[IO.Path]::IsPathRooted($segment)
        }
        catch {}

        if(!$segmentRooted)
        {
            continue
        }

        $candidate = join-path $segment $Path

        if(Test-Path $candidate -PathType Leaf)
        {
            return $candidate
        }
    }

    ThrowInvalidArgumentError "CannotFindRelativePath" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $LocalizedData.FileNotFound)
}


function AssertAbsolutePath
{
    [CmdletBinding()]
    param
    (
        $ParentBoundParameters,

        [System.String]
        [Parameter (ValueFromPipeline=$true)]
        $ParameterName,

        [switch]
        $Exist
    )

    Process
    {
        if(!$ParentBoundParameters.ContainsKey($ParameterName))
        {
            return
        }

        $path=$ParentBoundParameters[$ParameterName]

        if(!(IsRootedPath $Path))
        {
            ThrowInvalidArgumentError "PathShouldBeAbsolute" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f $ParameterName,$Path),
                $LocalizedData.PathShouldBeAbsolute)
        }

        if(!$Exist.IsPresent)
        {
            return
        }

        if(!(Test-Path $Path))
        {
            ThrowInvalidArgumentError "PathShouldExist" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f $ParameterName,$Path),
                $LocalizedData.PathShouldExist)
        }
    }
}

function AssertParameterIsNotSpecified
{
    [CmdletBinding()]
    param
    (
        $ParentBoundParameters,

        [System.String]
        [Parameter (ValueFromPipeline=$true)]
        $ParameterName
    )

    Process
    {
        if($ParentBoundParameters.ContainsKey($ParameterName))
        {
            ThrowInvalidArgumentError "ParameterShouldNotBeSpecified" ($LocalizedData.ParameterShouldNotBeSpecified -f $ParameterName)
        }
    }
}

function IsRootedPath
{
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Path
    )

    try
    {
        return [IO.Path]::IsPathRooted($Path)
    }
    catch
    {
        # if the Path has invalid characters like >, <, etc, we cannot determine if it is rooted so we do not go on
        ThrowInvalidArgumentError "CannotGetIsPathRooted" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $_.Exception.Message)
    }
}

function Write-Log
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Message
    )

    if ($PSCmdlet.ShouldProcess($Message, $null, $null))
    {
        Write-Verbose $Message
    }
}

function CallPInvoke
{
$script:ProgramSource = @"
using System;
using System.Collections.Generic;
using System.Text;
using System.Security;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Security.Principal;
using System.ComponentModel;
using System.IO;
 
namespace Source
{
    [SuppressUnmanagedCodeSecurity]
    public static class NativeMethods
    {
        //The following structs and enums are used by the various Win32 API's that are used in the code below
 
        [StructLayout(LayoutKind.Sequential)]
        public struct STARTUPINFO
        {
            public Int32 cb;
            public string lpReserved;
            public string lpDesktop;
            public string lpTitle;
            public Int32 dwX;
            public Int32 dwY;
            public Int32 dwXSize;
            public Int32 dwXCountChars;
            public Int32 dwYCountChars;
            public Int32 dwFillAttribute;
            public Int32 dwFlags;
            public Int16 wShowWindow;
            public Int16 cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        public struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public Int32 dwProcessID;
            public Int32 dwThreadID;
        }
 
        [Flags]
        public enum LogonType
        {
            LOGON32_LOGON_INTERACTIVE = 2,
            LOGON32_LOGON_NETWORK = 3,
            LOGON32_LOGON_BATCH = 4,
            LOGON32_LOGON_SERVICE = 5,
            LOGON32_LOGON_UNLOCK = 7,
            LOGON32_LOGON_NETWORK_CLEARTEXT = 8,
            LOGON32_LOGON_NEW_CREDENTIALS = 9
        }
 
        [Flags]
        public enum LogonProvider
        {
            LOGON32_PROVIDER_DEFAULT = 0,
            LOGON32_PROVIDER_WINNT35,
            LOGON32_PROVIDER_WINNT40,
            LOGON32_PROVIDER_WINNT50
        }
        [StructLayout(LayoutKind.Sequential)]
        public struct SECURITY_ATTRIBUTES
        {
            public Int32 Length;
            public IntPtr lpSecurityDescriptor;
            public bool bInheritHandle;
        }
 
        public enum SECURITY_IMPERSONATION_LEVEL
        {
            SecurityAnonymous,
            SecurityIdentification,
            SecurityImpersonation,
            SecurityDelegation
        }
 
        public enum TOKEN_TYPE
        {
            TokenPrimary = 1,
            TokenImpersonation
        }
 
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        internal struct TokPriv1Luid
        {
            public int Count;
            public long Luid;
            public int Attr;
        }
 
        public const int GENERIC_ALL_ACCESS = 0x10000000;
        public const int CREATE_NO_WINDOW = 0x08000000;
        internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
        internal const int TOKEN_QUERY = 0x00000008;
        internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
        internal const string SE_INCRASE_QUOTA = "SeIncreaseQuotaPrivilege";
 
        [DllImport("kernel32.dll",
              EntryPoint = "CloseHandle", SetLastError = true,
              CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern bool CloseHandle(IntPtr handle);
 
        [DllImport("advapi32.dll",
              EntryPoint = "CreateProcessAsUser", SetLastError = true,
              CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
        public static extern bool CreateProcessAsUser(
            IntPtr hToken,
            string lpApplicationName,
            string lpCommandLine,
            ref SECURITY_ATTRIBUTES lpProcessAttributes,
            ref SECURITY_ATTRIBUTES lpThreadAttributes,
            bool bInheritHandle,
            Int32 dwCreationFlags,
            IntPtr lpEnvrionment,
            string lpCurrentDirectory,
            ref STARTUPINFO lpStartupInfo,
            ref PROCESS_INFORMATION lpProcessInformation
            );
 
        [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
        public static extern bool DuplicateTokenEx(
            IntPtr hExistingToken,
            Int32 dwDesiredAccess,
            ref SECURITY_ATTRIBUTES lpThreadAttributes,
            Int32 ImpersonationLevel,
            Int32 dwTokenType,
            ref IntPtr phNewToken
            );
 
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern Boolean LogonUser(
            String lpszUserName,
            String lpszDomain,
            String lpszPassword,
            LogonType dwLogonType,
            LogonProvider dwLogonProvider,
            out IntPtr phToken
            );
 
        [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
        internal static extern bool AdjustTokenPrivileges(
            IntPtr htok,
            bool disall,
            ref TokPriv1Luid newst,
            int len,
            IntPtr prev,
            IntPtr relen
            );
 
        [DllImport("kernel32.dll", ExactSpelling = true)]
        internal static extern IntPtr GetCurrentProcess();
 
        [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
        internal static extern bool OpenProcessToken(
            IntPtr h,
            int acc,
            ref IntPtr phtok
            );
 
        [DllImport("advapi32.dll", SetLastError = true)]
        internal static extern bool LookupPrivilegeValue(
            string host,
            string name,
            ref long pluid
            );
 
        public static void CreateProcessAsUser(string strCommand, string strDomain, string strName, string strPassword)
        {
            var hToken = IntPtr.Zero;
            var hDupedToken = IntPtr.Zero;
            TokPriv1Luid tp;
            var pi = new PROCESS_INFORMATION();
            var sa = new SECURITY_ATTRIBUTES();
            sa.Length = Marshal.SizeOf(sa);
            Boolean bResult = false;
            try
            {
                bResult = LogonUser(
                    strName,
                    strDomain,
                    strPassword,
                    LogonType.LOGON32_LOGON_BATCH,
                    LogonProvider.LOGON32_PROVIDER_DEFAULT,
                    out hToken
                    );
                if (!bResult)
                {
                    throw new Win32Exception("The user could not be logged on. Ensure that the user has an existing profile on the machine and that correct credentials are provided. Logon error #" + Marshal.GetLastWin32Error().ToString());
                }
                IntPtr hproc = GetCurrentProcess();
                IntPtr htok = IntPtr.Zero;
                bResult = OpenProcessToken(
                        hproc,
                        TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
                        ref htok
                    );
                if(!bResult)
                {
                    throw new Win32Exception("Open process token error #" + Marshal.GetLastWin32Error().ToString());
                }
                tp.Count = 1;
                tp.Luid = 0;
                tp.Attr = SE_PRIVILEGE_ENABLED;
                bResult = LookupPrivilegeValue(
                    null,
                    SE_INCRASE_QUOTA,
                    ref tp.Luid
                    );
                if(!bResult)
                {
                    throw new Win32Exception("Error in looking up privilege of the process. This should not happen if DSC is running as LocalSystem Lookup privilege error #" + Marshal.GetLastWin32Error().ToString());
                }
                bResult = AdjustTokenPrivileges(
                    htok,
                    false,
                    ref tp,
                    0,
                    IntPtr.Zero,
                    IntPtr.Zero
                    );
                if(!bResult)
                {
                    throw new Win32Exception("Token elevation error #" + Marshal.GetLastWin32Error().ToString());
                }
 
                bResult = DuplicateTokenEx(
                    hToken,
                    GENERIC_ALL_ACCESS,
                    ref sa,
                    (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
                    (int)TOKEN_TYPE.TokenPrimary,
                    ref hDupedToken
                    );
                if(!bResult)
                {
                    throw new Win32Exception("Duplicate Token error #" + Marshal.GetLastWin32Error().ToString());
                }
                var si = new STARTUPINFO();
                si.cb = Marshal.SizeOf(si);
                si.lpDesktop = "";
                bResult = CreateProcessAsUser(
                    hDupedToken,
                    null,
                    strCommand,
                    ref sa,
                    ref sa,
                    false,
                    0,
                    IntPtr.Zero,
                    null,
                    ref si,
                    ref pi
                    );
                if(!bResult)
                {
                    throw new Win32Exception("The process could not be created. Create process as user error #" + Marshal.GetLastWin32Error().ToString());
                }
            }
            finally
            {
                if (pi.hThread != IntPtr.Zero)
                {
                    CloseHandle(pi.hThread);
                }
                if (pi.hProcess != IntPtr.Zero)
                {
                    CloseHandle(pi.hProcess);
                }
                 if (hDupedToken != IntPtr.Zero)
                {
                    CloseHandle(hDupedToken);
                }
            }
        }
    }
}
 
"@

            Add-Type -TypeDefinition $ProgramSource -ReferencedAssemblies "System.ServiceProcess"
}

Export-ModuleMember -function Get-TargetResource, Set-TargetResource, Test-TargetResource