SecurityFever.psm1

<#
    .SYNOPSIS
        List the current audit policy setting on the local system.
 
    .DESCRIPTION
        This command uses the auditpol.exe command to get the current audit
        policy setting for the local system and parses the output into a custom
        object.
 
    .INPUTS
        None.
 
    .OUTPUTS
        SecurityFever.AuditPolicy. Array of custom audit policy objects.
 
    .EXAMPLE
        PS C:\> Get-SecurityAuditPolicy
        Return all local security audit policies.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function Get-SecurityAuditPolicy
{
    [CmdletBinding()]
    param ()

    # Because the auditpol.exe cmdlet need administration permission, verify if
    # the current session is startet as administrator.
    if (-not (Test-AdministratorRole))
    {
        throw 'Access denied. Please start this functions as an administrator.'
    }

    # Use the helper functions to execute the auditpol.exe queries.
    $csvAuditCategories = Invoke-AuditPolListSubcategoryAllCsv | ConvertFrom-Csv
    $csvAuditSettings   = Invoke-AuditPolGetCategoryAllCsv | ConvertFrom-Csv

    foreach ($csvAuditCategory in $csvAuditCategories)
    {
        # If the Category/Subcategory field starts with two blanks, it is a
        # subcategory entry - else a category entry.
        if ($csvAuditCategory.'GUID' -like '{*-797A-11D9-BED3-505054503030}')
        {
            $lastCategory     = $csvAuditCategory.'Category/Subcategory'
            $lastCategoryGuid = $csvAuditCategory.GUID
        }
        else
        {
            $csvAuditSetting = $csvAuditSettings | Where-Object { $_.'Subcategory GUID' -eq $csvAuditCategory.GUID }

            $auditPolicy = New-Object -TypeName PSObject -Property @{
                ComputerName    = $csvAuditSetting.'Machine Name'
                Category        = $lastCategory
                CategoryGuid    = $lastCategoryGuid
                Subcategory     = $csvAuditSetting.'Subcategory'
                SubcategoryGuid = $csvAuditSetting.'Subcategory GUID'
                AuditSuccess    = $csvAuditSetting.'Inclusion Setting' -like '*Success*'
                AuditFailure    = $csvAuditSetting.'Inclusion Setting' -like '*Failure*'
            }

            $auditPolicy.PSTypeNames.Insert(0, 'SecurityFever.AuditPolicy')

            Write-Output $auditPolicy
        }
    }
}

<#
    .SYNOPSIS
        Get one audit policy setting on the local system.
 
    .DESCRIPTION
        This command uses the auditpol.exe command to get the current audit
        policy setting for the local system and parses the output for the target
        setting.
 
    .INPUTS
        None.
 
    .OUTPUTS
        System.Boolean. Return true if the audit policy is enabled, false if not.
 
    .EXAMPLE
        PS C:\> Get-SecurityAuditPolicySetting -Category 'Object Access' -Subcategory 'File System' -Setting 'Success'
        Returns the current setting for success audit on the file system object
        access audit policy.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function Get-SecurityAuditPolicySetting
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        # Audit policy category
        [Parameter(Mandatory = $true)]
        [System.String]
        $Category,

        # Audit policy subcategory
        [Parameter(Mandatory = $true)]
        [System.String]
        $Subcategory,

        # Audit policy setting
        [Parameter(Mandatory = $true)]
        [ValidateSet('Success', 'Failure')]
        [System.String]
        $Setting
    )

    $auditPolicies = Get-SecurityAuditPolicy

    foreach ($auditPolicy in $auditPolicies)
    {
        if ($auditPolicy.Category -eq $Category -and $auditPolicy.Subcategory -eq $Subcategory)
        {
            switch ($Setting)
            {
                'Success' { return $auditPolicy.AuditSuccess }
                'Failure' { return $auditPolicy.AuditFailure }
            }
        }
    }

    throw "No audit policy found for category $Category and subcategory $Subcategory."
}

<#
    .SYNOPSIS
        Generate a Time-Base One-Time Password based on RFC 6238.
 
    .DESCRIPTION
        This command uses the reference implementation of RFC 6238 to calculate
        a Time-Base One-Time Password. It bases on the HMAC SHA-1 hash function
        to generate a shot living One-Time Password.
 
    .INPUTS
        None.
 
    .OUTPUTS
        System.String. The one time password.
 
    .EXAMPLE
        PS C:\> Get-TimeBasedOneTimePassword -SharedSecret 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
        Get the Time-Based One-Time Password at the moment.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
        https://tools.ietf.org/html/rfc6238
#>

function Get-TimeBasedOneTimePassword
{
    [CmdletBinding()]
    [Alias('Get-TOTP')]
    param
    (
        # Base 32 formatted shared secret (RFC 4648).
        [Parameter(Mandatory = $true)]
        [System.String]
        $SharedSecret,

        # The date and time for the target calculation, default is now (UTC).
        [Parameter(Mandatory = $false)]
        [System.DateTime]
        $Timestamp = (Get-Date).ToUniversalTime(),

        # Token length of the one-time password, default is 6 characters.
        [Parameter(Mandatory = $false)]
        [System.Int32]
        $Length = 6,

        # The hash method to calculate the TOTP, default is HMAC SHA-1.
        [Parameter(Mandatory = $false)]
        [System.Security.Cryptography.KeyedHashAlgorithm]
        $KeyedHashAlgorithm = (New-Object -TypeName 'System.Security.Cryptography.HMACSHA1'),

        # Baseline time to start counting the steps (T0), default is Unix epoch.
        [Parameter(Mandatory = $false)]
        [System.DateTime]
        $Baseline = '1970-01-01 00:00:00',

        # Interval for the steps in seconds (TI), default is 30 seconds.
        [Parameter(Mandatory = $false)]
        [System.Int32]
        $Interval = 30
    )

    # Generate the number of intervals between T0 and the timestamp (now) and
    # convert it to a byte array with the help of Int64 and the bit converter.
    $numberOfSeconds   = ($Timestamp - $Baseline).TotalSeconds
    $numberOfIntervals = [Convert]::ToInt64([Math]::Floor($numberOfSeconds / $Interval))
    $byteArrayInterval = [System.BitConverter]::GetBytes($numberOfIntervals)
    [Array]::Reverse($byteArrayInterval)

    # Use the shared secret as a key to convert the number of intervals to a
    # hash value.
    $KeyedHashAlgorithm.Key = Convert-Base32ToByte -Base32 $SharedSecret
    $hash = $KeyedHashAlgorithm.ComputeHash($byteArrayInterval)

    # Calculate offset, binary and otp according to RFC 6238 page 13.
    $offset = $hash[($hash.Length-1)] -band 0xf
    $binary = (($hash[$offset + 0] -band '0x7f') -shl 24) -bor
              (($hash[$offset + 1] -band '0xff') -shl 16) -bor
              (($hash[$offset + 2] -band '0xff') -shl 8) -bor
              (($hash[$offset + 3] -band '0xff'))
    $otpInt = $binary % ([Math]::Pow(10, $Length))
    $otpStr = $otpInt.ToString().PadLeft($Length, '0')

    Write-Output $otpStr
}

<#
    .SYNOPSIS
        Runs the specified command in an elevated context.
 
    .DESCRIPTION
        Runs the specified command in an elevated context. This is useful on
        Windows systems where the user account control is enabled. Input object
        and result objects are serialized using XML.
        It's important, the command does use the current user context. This
        means, the current user needs administrative permissions on the local
        system. If no file path or script block is specified, the current
        running process will be run as administrator.
 
    .INPUTS
        None.
 
    .OUTPUTS
        Output of the invoked script block or command.
 
    .EXAMPLE
        PS C:\> Invoke-Elevated
        Will start the current process, e.g. PowerShell Console or ISE, in an
        elevated session as Administrator.
 
    .EXAMPLE
        PS C:\> Invoke-Elevated -FilePath 'C:\Temp\script.ps1'
        Start the script in an elevated session and return the result.
 
    .EXAMPLE
        PS C:\> Invoke-Elevated -ScriptBlock { Get-DscLocalConfigurationManager }
        Start the script in an elevated session and return the result.
 
    .EXAMPLE
        PS C:\> Invoke-Elevated -ScriptBlock { param ($Path) Remove-Item -Path $Path } -ArgumentList 'C:\Windows\test.txt'
        Delete a file from the program files folder with elevated permission,
        beacuse a normal user account has no permissions.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function Invoke-Elevated
{
    [CmdletBinding(DefaultParameterSetName = 'None')]
    [Alias('sudo')]
    param
    (
        # The path to an executable program.
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'FilePath')]
        [ValidateScript({Test-Path -Path $_})]
        [System.String]
        $FilePath,

        # The script block to execute in an elevated context.
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'ScriptBlock')]
        [System.Management.Automation.ScriptBlock]
        $ScriptBlock,

        # Optional argument list for the program or the script block.
        [Parameter(Mandatory = $false, Position = 1)]
        [System.Object[]]
        $ArgumentList
    )

    if ($PSCmdlet.ParameterSetName -eq 'None')
    {
        # If no file path and script block was specified, just elevate the
        # current session for interactive use. For this, use the start info
        # object of the current process and start an elevated new one.
        $currentProcess = Get-Process -Id $PID

        $processStart = $currentProcess.StartInfo
        $processStart.FileName         = $currentProcess.Path
        $processStart.Verb             = 'RunAs'

        $process = New-Object -TypeName System.Diagnostics.Process
        $process.StartInfo = $processStart
        $process.Start() | Out-Null
    }

    if ($PSCmdlet.ParameterSetName -eq 'FilePath')
    {
        # If a file path instead of a script block was specified, just load the
        # file content and parse it as script block.
        $ScriptBlock = [System.Management.Automation.ScriptBlock]::Create((Get-Content -Path $FilePath -ErrorAction Stop -Raw))
    }

    if ($PSCmdlet.ParameterSetName -eq 'FilePath' -or $PSCmdlet.ParameterSetName -eq 'ScriptBlock')
    {
        try
        {
            # To transport the parameters, script outputs and the errors, we use
            # the CliXml object serialization and temporary files. This is
            # necessary because the elevated process runs in an elevated context
            $scriptBlockFile   = [System.IO.Path]::GetTempFileName() + '.xml'
            $argumentListFile  = [System.IO.Path]::GetTempFileName() + '.xml'
            $commandOutputFile = [System.IO.Path]::GetTempFileName() + '.xml'
            $commandErrorFile  = [System.IO.Path]::GetTempFileName() + '.xml'

            $ScriptBlock  | Export-Clixml -Path $scriptBlockFile
            $ArgumentList | Export-Clixml -Path $argumentListFile

            # Create a command string which contains all command executed in the
            # elevated session. The wrapper of the script block is needed to
            # pass the parameters and return all outputs objects and errors.
            $commandString = ''
            $commandString += 'Set-Location -Path "{0}";' -f $pwd.Path
            $commandString += '$scriptBlock = [System.Management.Automation.ScriptBlock]::Create((Import-Clixml -Path "{0}"));' -f $scriptBlockFile
            $commandString += '$argumentList = [System.Object[]] (Import-Clixml -Path "{0}");' -f $argumentListFile
            $commandString += '$output = Invoke-Command -ScriptBlock $scriptBlock -ArgumentList $argumentList;'
            $commandString += '$error | Export-Clixml -Path "{0}";' -f $commandErrorFile
            $commandString += '$output | Export-Clixml -Path "{0}";' -f $commandOutputFile

            $commandEncoded = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($commandString))

            $processStart = New-Object -TypeName System.Diagnostics.ProcessStartInfo -ArgumentList 'C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe'
            $processStart.Arguments   = '-NoProfile -NonInteractive -EncodedCommand {0}' -f $commandEncoded
            $processStart.Verb        = 'RunAs'
            $processStart.WindowStyle = 'Hidden'

            $process = New-Object -TypeName System.Diagnostics.Process
            $process.StartInfo = $processStart
            $process.Start() | Out-Null

            Write-Verbose "Elevated powershell.exe process started with id $($process.Id)."

            $process.WaitForExit()

            Write-Verbose "Elevated powershell.exe process stopped with exit code $($process.ExitCode)."

            if ((Test-Path -Path $commandErrorFile))
            {
                Import-Clixml -Path $commandErrorFile | ForEach-Object { Write-Error $_ }
            }

            if ((Test-Path -Path $commandOutputFile))
            {
                Import-Clixml -Path $commandOutputFile | Write-Output
            }
        }
        catch
        {
            throw $_
        }
        finally
        {
            if ($null -ne $process)
            {
                $process.Dispose()
            }

            Remove-Item -Path $scriptBlockFile   -Force -ErrorAction SilentlyContinue
            Remove-Item -Path $argumentListFile  -Force -ErrorAction SilentlyContinue
            Remove-Item -Path $commandOutputFile -Force -ErrorAction SilentlyContinue
            Remove-Item -Path $commandErrorFile  -Force -ErrorAction SilentlyContinue
        }
    }
}

<#
    .SYNOPSIS
        Start a new PowerShell Console session.
 
    .DESCRIPTION
        Start a new PowerShell Console session with alternative credentials. It
        uses the Start-Process cmdlet and use the system drive as a working
        directory.
 
    .INPUTS
        None.
 
    .OUTPUTS
        None.
 
    .EXAMPLE
        PS C:\> Invoke-PowerShell -Credential 'DOMAIN\user'
        Start a new PowerShell Console session with alternative credentials.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function Invoke-PowerShell
{
    [CmdletBinding()]
    [Alias('posh')]
    param
    (
        # Alternative credentials to start a PowerShell Console session.
        [Parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential
    )

    Start-Process -FilePath "$PSHOME\powershell.exe" -WorkingDirectory $Env:SystemDrive -Credential $Credential
}

<#
    .SYNOPSIS
        Convert a string into a secure string.
 
    .DESCRIPTION
        Uses the Windows build-in data protection API (DPAPI) to convert the
        string to a secure string. Only the current user, on the current
        computer with the current profile can decrypt the secure string.
 
    .INPUTS
        System.String. The String to protect.
 
    .OUTPUTS
        System.Security.SecureString. The protected string.
 
    .EXAMPLE
        PS C:\> Protect-String -String 'Passw0rd'
        Protect the password a secure string.
 
    .EXAMPLE
        PS C:\> 'Text A', 'Text B' | Protect-String
        Protect both strings as a secure string.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function Protect-String
{
    [CmdletBinding()]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Scope='Function', Target='Protect-String')]
    [OutputType([System.Security.SecureString])]
    param
    (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [System.String[]]
        $String
    )

    process
    {
        foreach ($currentString in $String)
        {
            $currentSecureString = ConvertTo-SecureString -String $currentString -AsPlainText -Force

            Write-Output $currentSecureString
        }
    }
}

<#
    .SYNOPSIS
        Test the provided credentials with the choosen test method against the
        local system or Active Directory.
 
    .DESCRIPTION
        Test the provided credentials against the local system by starting a
        simple process or against Active Directory by binding to the root via
        ADSI.
 
    .INPUTS
        System.Management.Automation.PSCredential
 
    .OUTPUTS
        System.Management.Automation.PSCredential
        System.Boolean
 
    .EXAMPLE
        PS C:\> Test-Credential -Credential 'DOMAIN\user'
        Test the interactive provided credentials against the local system. If
        the credential are not valid, an exception is thrown.
 
    .EXAMPLE
        PS C:\> Test-Credential -Credential 'DOMAIN\user' -Quiet
        Test the interactive provided credentials against the local system and
        return $true if the credentials are valid, else return $false.
 
    .EXAMPLE
        PS C:\> Test-Credential -Username $Username -Password $Password -Method ActiveDirectory
        Test the provided username and password pair against the Active
        Directory.
 
    .EXAMPLE
        PS C:\> $cred = Get-Credential 'DOMAIN\user' | Test-Credential
        Request the user to enter the password for DOMAIN\user and test it
        immediately with Test-Credential against the local system. If the
        credentials are valid, they are returned and stored in $cred. If not, an
        terminating exception is thrown.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function Test-Credential
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    [OutputType([System.Management.Automation.PSCredential])]
    param
    (
        # PowerShell credentials object to test.
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'Credential')]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential,

        # The username to validate. Specify password too.
        [Parameter(Mandatory = $true, ParameterSetName = 'UsernamePassword')]
        [System.String]
        $Username,

        # The password to validate. Specify username too.
        [Parameter(Mandatory = $true, ParameterSetName = 'UsernamePassword')]
        [System.Security.SecureString]
        $Password,

        # Validation method.
        [Parameter(Mandatory = $false)]
        [ValidateSet('StartProcess', 'ActiveDirectory')]
        [System.String]
        $Method = 'StartProcess',

        # Return a boolean value which indicates if the credentials are valid.
        [Parameter(Mandatory = $false)]
        [System.Management.Automation.SwitchParameter]
        $Quiet
    )

    begin
    {
        if ($PSCmdlet.ParameterSetName -eq 'UsernamePassword')
        {
            $Credential = New-Object -TypeName PSCredential -ArgumentList $Username, $Password
        }
    }

    process
    {
        $exception = $null

        if ($Method -eq 'StartProcess')
        {
            Write-Verbose "Test credentials $($Credential.UserName) by starting a local cmd.exe process"

            try
            {
                # Create a new local process with the given credentials. This
                # does not validate the credentials against an external target
                # system, but tests if they are valid locally. Of courese, it's
                # possible to validate domain credentials too.
                $startInfo = New-Object -TypeName System.Diagnostics.ProcessStartInfo
                $startInfo.FileName         = 'cmd.exe'
                $startInfo.WorkingDirectory = $env:SystemRoot
                $startInfo.Arguments        = '/C', 'echo %USERDOMAIN%\%USERNAME%'
                $startInfo.Domain           = $Credential.GetNetworkCredential().Domain
                $startInfo.UserName         = $Credential.GetNetworkCredential().UserName
                $startInfo.Password         = $Credential.GetNetworkCredential().SecurePassword
                $startInfo.WindowStyle      = [System.Diagnostics.ProcessWindowStyle]::Hidden
                $startInfo.CreateNoWindow   = $true
                $startInfo.UseShellExecute  = $false

                $process = New-Object -TypeName  System.Diagnostics.Process
                $process.StartInfo = $startInfo
                $process.Start() | Out-Null

                # If the process start does not throw an exception, the
                # credentials are valid.
            }
            catch
            {
                # Hide the $process.Start() method call exception, by expanding
                # the inner exception.
                if ($_.Exception.Message -like 'Exception calling "Start" with "0" argument(s):*')
                {
                    $exception = $_.Exception.InnerException
                }
                else
                {
                    $exception = $_.Exception
                }
            }
        }

        if ($Method -eq 'ActiveDirectory')
        {
            Write-Verbose "Test credentials $($Credential.UserName) by binding the default domain with ADSI"

            try
            {
                # We use an empty path, because we just test the credential
                # binding and not any object access in Active Directory.
                $directoryEntryArgs = @{
                        TypeName     = 'System.DirectoryServices.DirectoryEntry'
                        ArgumentList = '', # Bind to the local default domain
                                       $Credential.GetNetworkCredential().UserName,
                                       $Credential.GetNetworkCredential().Password
                }
                $directoryEntry = New-Object @directoryEntryArgs -ErrorAction Stop

                if ($null -eq $directoryEntry -or [String]::IsNullOrEmpty($directoryEntry.distinguishedName))
                {
                    throw 'Unable to create an ADSI connection.'
                }
            }
            catch
            {
                $exception = $_.Exception
            }
        }

        # Check the exception variable if an exception occured and return a
        # boolean or the credentials. In case of an exception, throw a custom
        # exception.
        if ($Quiet.IsPresent)
        {
            Write-Output ($null -eq $exception)
        }
        else
        {
            if ($null -eq $exception)
            {
                Write-Output $Credential
            }
            else
            {
                $errorRecordArgs = $exception, '0', [System.Management.Automation.ErrorCategory]::AuthenticationError, $Credential
                $errorRecord     = New-Object -TypeName 'System.Management.Automation.ErrorRecord' -ArgumentList $errorRecordArgs

                $PSCmdlet.ThrowTerminatingError($errorRecord)
            }
        }
    }
}

<#
    .SYNOPSIS
        Convert a secure string into a string.
 
    .DESCRIPTION
        Uses the Windows build-in data protection API (DPAPI) to convert the
        secure string back to a string. Only the user which has protected the
        original string can decrypt it.
 
    .INPUTS
        System.Security.SecureString. The protected secure string.
 
    .OUTPUTS
        System.String. The unprotected string.
 
    .EXAMPLE
        PS C:\> Unprotect-SecureString -SecureString $password
        Get the plain text password.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function Unprotect-SecureString
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [System.Security.SecureString[]]
        $SecureString
    )

    process
    {
        foreach ($currentSecureString in $SecureString)
        {
            $currentCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList 'Dummy', $currentSecureString

            $currentString = $currentCredential.GetNetworkCredential().Password

            Write-Output $currentString
        }
    }
}

<#
    .SYNOPSIS
        Show security relevant activities on a system.
 
    .DESCRIPTION
        Show security relevant activities on a system. This includes:
        - Startup / Shutdown
        - Awake / Sleep
        - Logon / Logoff
 
    .INPUTS
        None.
 
    .OUTPUTS
        SecurityFever.ActivityRecord. Array of custom activity records.
 
    .EXAMPLE
        PS C:\> Get-SecurityActivity
        Get all available security activity records on the system.
 
    .EXAMPLE
        PS C:\> Get-SecurityActivity -Activity Startup, Shutdown
        Get only the startup and shutdown activity records on the system.
 
    .EXAMPLE
        PS C:\> Get-SecurityActivity -Recommended
        Get all available security activity records on the system but show just
        the recommended records and hide verbose records.
 
    .EXAMPLE
        PS C:\> Get-SecurityActivity -ComputerName 'COMPUTER' -Credential 'DOMAIN\User'
        Get all security activity records on the remote system 'COMPUTER'.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function Get-SecurityActivity
{
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', Scope='Function', Target='Get-SecurityActivity')]
    [CmdletBinding(DefaultParameterSetName = 'Local')]
    param
    (
        # Define the activity action(s).
        [Parameter(Mandatory = $false)]
        [ValidateSet('Startup', 'Shutdown', 'Awake', 'Sleep', 'Logon', 'Logoff')]
        [System.String[]]
        $Activity,

        # Specify a remote computer to query.
        [Parameter(Mandatory = $true, ParameterSetName = 'Remote')]
        [System.String]
        $ComputerName,

        # Specify credentials for the remote computer.
        [Parameter(Mandatory = $false, ParameterSetName = 'Remote')]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential,

        # Specify a time limit for the records.
        [Parameter(Mandatory = $false)]
        [System.DateTime]
        $After = ([DateTime]::MinValue),

        # Show only filtered recommended events.
        [Parameter(Mandatory = $false)]
        [System.Management.Automation.SwitchParameter]
        $Recommended
    )

    # The security event log is protected, therefore the cmdlet need elevated
    # permission. Verify if the current session is startet as administrator.
    if ($PSCmdlet.ParameterSetName -eq 'Local' -and -not (Test-AdministratorRole))
    {
        throw 'Access denied. Please start this functions as an administrator.'
    }

    # If no activity is specified, initialize the input with all possible
    # activities.
    if ($null -eq $Activity -or $Activity.Count -eq 0)
    {
        $Activity = (Get-Command -Name 'Get-SecurityActivity').Parameters['Activity'].Attributes.ValidValues
    }

    # Static event log map to get the event log id's for each activity.
    $eventLogMap = @{
        4608 = @{ Activity = 'Startup';  Log = 'Security'; Event  = 'LSASS Started'       }   # Windows is starting up.
        6005 = @{ Activity = 'Startup';  Log = 'System';   Event  = 'Event Log Started'   }   # The Event log service was started.
        6009 = @{ Activity = 'Startup';  Log = 'System';   Event  = 'OS Initialization'   }   # Microsoft (R) Windows (R) <Version>. <Build> Multiprocessor Free.

        1074 = @{ Activity = 'Shutdown'; Log = 'System';   Event  = 'Requested Shutdown'  }   # The process <Process> has initiated the power off of computer <Computer> on behalf of user <Domain>\<User> for the following reason: ...
        6006 = @{ Activity = 'Shutdown'; Log = 'System';   Event  = 'Event Log Stopped'   }   # The Event log service was stopped.
        6008 = @{ Activity = 'Shutdown'; Log = 'System';   Event  = 'Unexpected Shutdown' }   # The previous system shutdown at <Time> on <Date> was unexpected.

        1    = @{ Activity = 'Awake';    Log = 'System';   Event  = 'Leaving Sleep'       }   # The system has returned from a low power state.

        42   = @{ Activity = 'Sleep';    Log = 'System';   Event  = 'Entering Sleep'      }   # The system is entering sleep.

        4624 = @{ Activity = 'Logon';    Log = 'Security'; Event  = 'Logon Successful'    }   # An account was successfully logged on.
        4625 = @{ Activity = 'Logon';    Log = 'Security'; Event  = 'Logon Failed'        }   # An account failed to log on.

        4634 = @{ Activity = 'Logoff';   Log = 'Security'; Event  = 'Logoff Successful'   }   # An account was logged off.
        4647 = @{ Activity = 'Logoff';   Log = 'Security'; Event  = 'Logoff Request'      }   # User initiated logoff.

        # 4648 = @{ Activity = 'Logon' } # A logon was attempted using explicit credentials
        # 4778 = @{ Activity = 'SessionReconnected' }
        # 4779 = @{ Activity = 'SessionDisconnected' }
        # 4800 = @{ Activity = 'WorkstationLocked' }
        # 4801 = @{ Activity = 'WorkstationUnlocked' }
        # 4802 = @{ Activity = 'ScreensaverInvoked' }
        # 4803 = @{ Activity = 'ScreensaverDismissed' }
    }

    # Warning messages, if the audit policy is disabled for the requested
    # activity.
    if ($PSCmdlet.ParameterSetName -eq 'Local')
    {
        if ($Activity -contains 'Startup')
        {
            if(-not (Get-SecurityAuditPolicySetting -Category 'System' -Subcategory 'Security State Change' -Setting 'Success'))
            {
                Write-Warning "System audit policy for 'System' > 'Security State Change' > 'Success' is not enabled."
            }
        }
        if ($Activity -contains 'Logon')
        {
            if(-not (Get-SecurityAuditPolicySetting -Category 'Logon/Logoff' -Subcategory 'Logon' -Setting 'Success'))
            {
                Write-Warning "System audit policy for 'Logon/Logoff' > 'Logon' > 'Success' is not enabled."
            }
            if(-not (Get-SecurityAuditPolicySetting -Category 'Logon/Logoff' -Subcategory 'Logon' -Setting 'Failure'))
            {
                Write-Warning "System audit policy for 'Logon/Logoff' > 'Logon' > 'Failure' is not enabled."
            }
        }
        if ($Activity -contains 'Logoff')
        {
            if(-not (Get-SecurityAuditPolicySetting -Category 'Logon/Logoff' -Subcategory 'Logoff' -Setting 'Success'))
            {
                Write-Warning "System audit policy for 'Logon/Logoff' > 'Logoff' > 'Success' is not enabled."
            }
        }
    }
    if ($PSCmdlet.ParameterSetName -eq 'Remote')
    {
        Write-Warning "Unable to verify the audit policy settings for $ComputerName."
    }

    # Build the xml filter to query the system and security log.
    $filterSystem   = ''
    $filterSecurity = ''
    $filterSystemIds   = $eventLogMap.GetEnumerator() | Where-Object { $_.Value.Activity -in $Activity -and $_.Value.Log -eq 'System' } | ForEach-Object Name
    $filterSecurityIds = $eventLogMap.GetEnumerator() | Where-Object { $_.Value.Activity -in $Activity -and $_.Value.Log -eq 'Security' } | ForEach-Object Name
    if ($filterSystemIds.Count -gt 0)
    {
        $filterSystem = '<Select Path="System">*[System[(({0}) and TimeCreated[@SystemTime&gt;=''{1}''])]]</Select>' -f ([String]::Join(' or ', ($filterSystemIds | ForEach-Object { "EventID=$_" }))), $After.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')
    }
    if ($filterSecurityIds.Count -gt 0)
    {
        $filterSecurity = '<Select Path="Security">*[System[(({0}) and TimeCreated[@SystemTime&gt;=''{1}''])]]</Select>' -f ([String]::Join(' or ', ($filterSecurityIds | ForEach-Object { "EventID=$_" }))), $After.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')
    }
    $filter = '<QueryList><Query Id="0" Path="Security">{0}{1}</Query></QueryList>' -f $filterSystem, $filterSecurity

    # Execute query against local or remote system.
    if ($PSCmdlet.ParameterSetName -eq 'Local')
    {
        $events = Get-WinEvent -FilterXml $filter -ErrorAction Stop
    }
    if ($PSCmdlet.ParameterSetName -eq 'Remote')
    {
        $invokeCommandParam = @{
            ComputerName = $ComputerName
            ScriptBlock  = { param ($filter) Get-WinEvent -FilterXml $filter -ErrorAction Stop | Select-Object Id, MachineName, TimeCreated, Properties }
            ArgumentList = $filter
        }
        if ($null -ne $Credential)
        {
            $invokeCommandParam['Credential'] = $Credential
        }

        $events = Invoke-Command @invokeCommandParam -ErrorAction Stop
    }

    # Security activities
    $activities = @()

    foreach ($event in $events)
    {
        switch ($event.Id)
        {
            4608 { $activities += Convert-EventLogObject -Record $event -Map $eventLogMap }
            6005 { $activities += Convert-EventLogObject -Record $event -Map $eventLogMap }
            6009 { $activities += Convert-EventLogObject -Record $event -Map $eventLogMap }

            1074 { $activities += Convert-EventLogObjectId1074 -Record $event -Map $eventLogMap }
            6006 { $activities += Convert-EventLogObject -Record $event -Map $eventLogMap }
            6008 { $activities += Convert-EventLogObjectId6008 -Record $event -Map $eventLogMap }

            1    { if ($event.ProviderName -eq 'Microsoft-Windows-Power-Troubleshooter') { $activities += Convert-EventLogObjectId1 -Record $event -Map $eventLogMap } }

            42   { $activities += Convert-EventLogObjectId42 -Record $event -Map $eventLogMap }

            4624 { $activities += Convert-EventLogObjectId4624 -Record $event -Map $eventLogMap }
            4625 { $activities += Convert-EventLogObjectId4625 -Record $event -Map $eventLogMap }

            4634 { $activities += Convert-EventLogObjectId4634 -Record $event -Map $eventLogMap }
            4647 { $activities += Convert-EventLogObjectId4647 -Record $event -Map $eventLogMap }
        }
    }

    # Filter for recommended activities
    if ($Recommended.IsPresent)
    {
        $activities = $activities | Where-Object { -not (
            $_.Id -eq 6006 -or
            $_.Id -eq 6005 -or
            $_.Id -eq 4608 -or
            ($_.Id -eq 4624 -and 'Network', 'Batch', 'Service', 'Unlock', 'NetworkCleartext', 'NewCredentials' -contains $_.Type) -or
            ($_.Id -eq 4624 -and $_.Username -like 'Window Manager\DWM-*') -or
            ($_.Id -eq 4634 -and 'Network', 'Batch', 'Service', 'Unlock', 'NetworkCleartext', 'NewCredentials' -contains $_.Type) -or
            ($_.Id -eq 4634 -and $_.Username -like 'Window Manager\DWM-*')
        ) }
    }

    $activities | Sort-Object TimeCreated -Descending | Write-Output
}

<#
    .SYNOPSIS
        Get the current impersonation context and the active windows identity.
 
    .DESCRIPTION
        Returns the current impersonation context and the active windows
        identity available on the GetCurrent() method on the WindowsIdentity
        .NET class.
 
    .INPUTS
        None.
 
    .OUTPUTS
        The current impersonation context.
 
    .EXAMPLE
        PS C:\> Get-ImpersonationContext
        Return the current impersonation context.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function Get-ImpersonationContext
{
    [CmdletBinding()]
    param ()

    Initialize-ImpersonationContext

    $windowsIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent()

    [PSCustomObject] @{
        ImpersonationLevel = $windowsIdentity.ImpersonationLevel
        ImpersonationStack = $Script:ImpersonationContext.Count
        WindowsIdentity    = $windowsIdentity.Name
        AuthenticationType = $windowsIdentity.AuthenticationType
        IsAuthenticated    = $windowsIdentity.IsAuthenticated
        IsGuest            = $windowsIdentity.IsGuest
        IsSystem           = $windowsIdentity.IsSystem
        IsAnonymous        = $windowsIdentity.IsAnonymous
    }
}

<#
    .SYNOPSIS
        Leave the current impersonation context.
 
    .DESCRIPTION
        If the current session was impersonated with Push-ImpersonationContext,
        this command will leave the impersonation context.
 
    .INPUTS
        None.
 
    .OUTPUTS
        None.
 
    .EXAMPLE
        PS C:\> Pop-ImpersonationContext
        Leave the current impersonation context.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function Pop-ImpersonationContext
{
    [CmdletBinding()]
    param ()

    Initialize-ImpersonationContext

    if ($Script:ImpersonationContext.Count -gt 0)
    {
        # Get the latest impersonation context
        $popImpersonationContext = $Script:ImpersonationContext.Pop()

        # Undo the impersonation
        $popImpersonationContext.Undo()

        # Reset the PSReadline history save style
        if ($null -ne (Get-Module -Name 'PSReadline') -and $Script:ImpersonationContext.Count -eq 0)
        {
            Set-PSReadlineOption -HistorySaveStyle $Script:PSReadlineHistorySaveStyle -ErrorAction SilentlyContinue
        }
    }
}

<#
    .SYNOPSIS
        Create a new impersonation context by using the specified credentials.
        All following commands will be executed as the specified user until the
        context is closed.
 
    .DESCRIPTION
        Use the Win32 unmanaged API in the AdvApi32.dll to logon the user with
        the specified credentials. With this logon token, the user can be
        impersonated in the current session.
 
    .INPUTS
        None.
 
    .OUTPUTS
        None.
 
    .EXAMPLE
        PS C:\> Push-ImpersonationContext -Credential 'CONTOSO\Operator'
        Create a new impersonation context for the Contoso Operator user.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function Push-ImpersonationContext
{
    [CmdletBinding()]
    param
    (
        # Specifies a user account to impersonate.
        [Parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential,

        # The logon type.
        [Parameter(Mandatory = $false)]
        [ValidateSet('Interactive', 'Network', 'Batch', 'Service', 'Unlock', 'NetworkClearText', 'NewCredentials')]
        $LogonType = 'Interactive',

        # The logon provider.
        [Parameter(Mandatory = $false)]
        [ValidateSet('Default', 'WinNT40', 'WinNT50')]
        $LogonProvider = 'Default'
    )

    Initialize-ImpersonationContext

    # Handle for the logon token
    $tokenHandle = [IntPtr]::Zero

    # Now logon the user account on the local system
    $logonResult = [Win32.AdvApi32]::LogonUser($Credential.GetNetworkCredential().UserName,
                                               $Credential.GetNetworkCredential().Domain,
                                               $Credential.GetNetworkCredential().Password,
                                               ([Win32.Logon32Type] $LogonType),
                                               ([Win32.Logon32Provider] $LogonProvider),
                                               [ref] $tokenHandle)

    # Error handling, if the logon fails
    if (-not $logonResult)
    {
        $errorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()

        throw "Failed to call LogonUser() throwing Win32 exception with error code: $errorCode"
    }

    # Update the PSReadline history save style
    if ($null -ne (Get-Module -Name 'PSReadline') -and $Script:ImpersonationContext.Count -eq 0)
    {
        Set-PSReadlineOption -HistorySaveStyle 'SaveNothing' -ErrorAction SilentlyContinue
    }

    # Go to the system root drive, to prevent access denied on user paths
    Set-Location -Path "$Env:SystemRoot\"

    # Now, impersonate the new user account
    $newImpersonationContext = [System.Security.Principal.WindowsIdentity]::Impersonate($tokenHandle)
    $Script:ImpersonationContext.Push($newImpersonationContext)

    # Finally, close the handle to the token
    [Win32.Kernel32]::CloseHandle($tokenHandle) | Out-Null
}

<#
    .SYNOPSIS
        Add an entry to the trusted host list.
 
    .DESCRIPTION
        Append the entry to the trusted host list separated by a comma and store
        it in the path WSMan:\localhost\Client\TrustedHosts.
 
    .INPUTS
        System.String. Trusted host list entry.
 
    .OUTPUTS
        None.
 
    .EXAMPLE
        PS C:\> Add-TrustedHosts -ComputerName 'SERVER', '10.0.0.1', '*.contoso.com'
        Add the three entries to the trusted host list.
 
    .EXAMPLE
        PS C:\> '10.0.0.1', '10.0.0.2', '10.0.0.3' | Add-TrustedHosts
        Add the list of IP addresses to the trusted host list.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function Add-TrustedHost
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [System.String[]]
        $ComputerName
    )

    begin
    {
        # The trusted hosts list can only be changed as an administrator.
        if (-not (Test-AdministratorRole))
        {
            throw 'Access denied. Please start this functions as an administrator.'
        }

        # Get the WSMan trusted hosts item, ensure its a string
        $trustedHosts = [String] (Get-Item -Path 'WSMan:\localhost\Client\TrustedHosts').Value
    }

    process
    {
        # Add all new entries
        foreach ($computer in $ComputerName)
        {
            $trustedHosts = '{0},{1}' -f $trustedHosts, $computer
            $trustedHosts = $trustedHosts.Trim(',')
        }
    }

    end
    {
        if ($PSCmdlet.ShouldProcess($trustedHosts, "Set"))
        {
            # Finally, set the item
            Set-Item -Path 'WSMan:\localhost\Client\TrustedHosts' -Value $trustedHosts -Force
        }
    }
}

<#
    .SYNOPSIS
        Get trusted host list entries.
 
    .DESCRIPTION
        Return the WSMan:\localhost\Client\TrustedHosts item as string array
        separated by the comma.
 
    .INPUTS
        None.
 
    .OUTPUTS
        System.String. Array of trusted host list entries.
 
    .EXAMPLE
        PS C:\> Get-TrustedHosts
        Get trusted host list entries.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function Get-TrustedHost
{
    [CmdletBinding()]
    param ()

    # Get the WSMan trusted hosts item, ensure its a string
    $trustedHosts = [String] (Get-Item -Path 'WSMan:\localhost\Client\TrustedHosts').Value

    # Split the list by comma
    if (-not [String]::IsNullOrWhiteSpace($trustedHosts))
    {
        Write-Output $trustedHosts.Split(',')
    }
}

<#
    .SYNOPSIS
        Remove an entry from the trusted host list.
 
    .DESCRIPTION
        Remove an entry from the trusted host list and regenerate a new list
        with all the remaining entries, separated by a comma, and store it in
        the path WSMan:\localhost\Client\TrustedHosts.
 
    .INPUTS
        System.String. Trusted host list entry.
 
    .OUTPUTS
        None.
 
    .EXAMPLE
        PS C:\> Remove-TrustedHosts -ComputerName 'SERVER', '10.0.0.1', '*.contoso.com'
        Remove three entries from the trusted host list.
 
    .EXAMPLE
        PS C:\> '10.0.0.1', '10.0.0.2', '10.0.0.3' | Remove-TrustedHosts
        Remove the list of IP addresses from the trusted host list.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function Remove-TrustedHost
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [System.String[]]
        $ComputerName
    )

    begin
    {
        # The trusted hosts list can only be changed as an administrator.
        if (-not (Test-AdministratorRole))
        {
            throw 'Access denied. Please start this functions as an administrator.'
        }

        # Get the WSMan trusted hosts item, ensure its a string
        $trustedHosts = [String] (Get-Item -Path 'WSMan:\localhost\Client\TrustedHosts').Value

        # Create an array list
        $trustedHostsList = New-Object -TypeName 'System.Collections.ArrayList'
        $trustedHostsList.AddRange($trustedHosts.Split(','))
    }

    process
    {
        # Remove the entries
        foreach ($computer in $ComputerName)
        {
            if ($trustedHostsList.Contains($computer))
            {
                $trustedHostsList.Remove($computer)
            }
        }
    }

    end
    {
        # Join the remaining entries
        $trustedHosts = [String]::Join(',', @($trustedHostsList))

        if ($PSCmdlet.ShouldProcess($trustedHosts, "Set"))
        {
            # Finally, set the item
            Set-Item -Path 'WSMan:\localhost\Client\TrustedHosts' -Value $trustedHosts -Force
        }
    }
}

<#
    .SYNOPSIS
        Get the PSCredential objects from the Windows Credential Manager vault.
 
    .DESCRIPTION
        This cmdlet uses the native unmanaged Win32 api to retrieve all entries
        from the Windows Credential Manager vault. The entries are of type
        PSCredential. To get the full credential entries with all properties
        like target name, use the Get-VaultEntry cmdlet.
 
    .INPUTS
        None.
 
    .OUTPUTS
        System.Management.Automation.PSCredential.
 
    .EXAMPLE
        PS C:\> Get-VaultCredential -TargetName 'MyUserCred'
        Return the PSCredential objects with the target name 'MyUserCred'.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function Get-VaultCredential
{
    [CmdletBinding()]
    [Alias('Get-VaultEntryCredential')]
    [OutputType([System.Management.Automation.PSCredential])]
    param
    (
        # Filter the credentials by target name. Does not support wildcards.
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [System.String]
        $TargetName,

        # Filter the credentials by type.
        [Parameter(Mandatory = $false)]
        [AllowNull()]
        [SecurityFever.CredentialManager.CredentialType]
        $Type,

        # Filter the credentials by persist location.
        [Parameter(Mandatory = $false)]
        [AllowNull()]
        [SecurityFever.CredentialManager.CredentialPersist]
        $Persist,

        # Filter the credentials by username. Does not support wildcards.
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [System.String]
        $Username
    )

    $credentialEntries = [SecurityFever.CredentialManager.CredentialStore]::GetCredentials($TargetName, $Type, $Persist, $Username)

    foreach ($credentialEntry in $credentialEntries)
    {
        Write-Output $credentialEntry.Credential
    }
}

<#
    .SYNOPSIS
        Get the credential entries from the Windows Credential Manager vault.
 
    .DESCRIPTION
        This cmdlet uses the native unmanaged Win32 api to retrieve all entries
        from the Windows Credential Manager vault. The entries are not objects
        of type PSCredential. The PSCredential is available on the Credential
        property or with the Get-VaultCredential cmdlet or you can get a secure
        string with the Get-VaultSecureString cmdlet.
 
    .INPUTS
        None.
 
    .OUTPUTS
        SecurityFever.CredentialManager.CredentialEntry.
 
    .EXAMPLE
        PS C:\> Get-VaultEntry
        Returns all available credential entries.
 
    .EXAMPLE
        PS C:\> Get-VaultEntry -TargetName 'MyUserCred'
        Return the credential entry with the target name 'MyUserCred'.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function Get-VaultEntry
{
    [CmdletBinding()]
    [OutputType([SecurityFever.CredentialManager.CredentialEntry])]
    param
    (
        # Filter the credentials by target name. Does not support wildcards.
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [System.String]
        $TargetName,

        # Filter the credentials by type.
        [Parameter(Mandatory = $false)]
        [AllowNull()]
        [SecurityFever.CredentialManager.CredentialType]
        $Type,

        # Filter the credentials by persist location.
        [Parameter(Mandatory = $false)]
        [AllowNull()]
        [SecurityFever.CredentialManager.CredentialPersist]
        $Persist,

        # Filter the credentials by username. Does not support wildcards.
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [System.String]
        $Username
    )

    $credentialEntries = [SecurityFever.CredentialManager.CredentialStore]::GetCredentials($TargetName, $Type, $Persist, $Username)

    foreach ($credentialEntry in $credentialEntries)
    {
        Write-Output $credentialEntry
    }
}

<#
    .SYNOPSIS
        Get the secure string objects from the Windows Credential Manager vault.
 
    .DESCRIPTION
        This cmdlet uses the native unmanaged Win32 api to retrieve all entries
        from the Windows Credential Manager vault. The entries are of type
        secure string. To get the full credential entries with all properties
        like target name, use the Get-VaultEntry cmdlet.
 
    .INPUTS
        None.
 
    .OUTPUTS
        System.Security.SecureString.
 
    .EXAMPLE
        PS C:\> Get-VaultSecureString -TargetName 'MyUserCred'
        Return the secure string objects with the target name 'MyUserCred'.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function Get-VaultSecureString
{
    [CmdletBinding()]
    [Alias('Get-VaultEntrySecureString')]
    [OutputType([System.Security.SecureString])]
    param
    (
        # Filter the credentials by target name. Does not support wildcards.
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [System.String]
        $TargetName,

        # Filter the credentials by type.
        [Parameter(Mandatory = $false)]
        [AllowNull()]
        [SecurityFever.CredentialManager.CredentialType]
        $Type,

        # Filter the credentials by persist location.
        [Parameter(Mandatory = $false)]
        [AllowNull()]
        [SecurityFever.CredentialManager.CredentialPersist]
        $Persist,

        # Filter the credentials by username. Does not support wildcards.
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [System.String]
        $Username
    )

    $credentialEntries = [SecurityFever.CredentialManager.CredentialStore]::GetCredentials($TargetName, $Type, $Persist, $Username)

    foreach ($credentialEntry in $credentialEntries)
    {
        Write-Output $credentialEntry.Credential.Password
    }
}

<#
    .SYNOPSIS
        Create a new entry in the Windows Credential Manager vault.
 
    .DESCRIPTION
        This cmdlet uses the native unmanaged Win32 api to create a new entry in
        the Windows Credential Manager vault. The credential type and persist
        location can be specified. By default, a generic entry with no special
        purpose is created on the local machine persist location.
 
        Use one of the following persist locations:
        - Session
          The credential persists for the life of the logon session. It will not
          be visible to other logon sessions of this same user. It will not
          exist after this user logs off and back on.
        - LocalMachine
          The credential persists for all subsequent logon sessions on this same
          computer. It is visible to other logon sessions of this same user on
          this same computer and not visible to logon sessions for this user on
          other computers.
        - Enterprise
          The credential persists for all subsequent logon sessions on this same
          computer. It is visible to other logon sessions of this same user on
          this same computer and to logon sessions for this user on other
          computers.
 
        Use on of the following types:
        - Generic
          The credential is a generic credential. The credential will not be
          used by any particular authentication package. The credential will be
          stored securely but has no other significant characteristics.
        - DomainPassword
          The credential is a password credential and is specific to Microsoft's
          authentication packages. The NTLM, Kerberos, and Negotiate
          authentication packages will automatically use this credential when
          connecting to the named target.
        - DomainCertificate
          The credential is a certificate credential and is specific to
          Microsoft's authentication packages. The Kerberos, Negotiate, and
          Schannel authentication packages automatically use this credential
          when connecting to the named target.
        - DomainVisiblePassword
          This value is no longer supported. The credential is a password
          credential and is specific to authentication packages from Microsoft.
          The Passport authentication package will automatically use this
          credential when connecting to the named target.
        - GenericCertificate
          The credential is a certificate credential that is a generic
          authentication package.
        - DomainExtended
          The credential is supported by extended Negotiate packages.
        - Maximum
          The maximum number of supported credential types.
        - MaximumEx
          The extended maximum number of supported credential types that now
          allow new applications to run on older operating systems.
 
    .INPUTS
        None.
 
    .OUTPUTS
        SecurityFever.CredentialManager.CredentialEntry.
 
    .EXAMPLE
        PS C:\> New-VaultEntry -TargetName 'MyUserCred' -Credential $credential
        Create a new entry in the Credential Manager vault with the name
        MyUserCred and the credentials specified in the variable.
 
    .EXAMPLE
        PS C:\> New-VaultEntry -TargetName 'MyUserCred' -Username 'DOMAIN\user' -Password $secretPassword
        Create a new entry in the Credential Manager vault with the name
        MyUserCred, the username user and the password specified in the
        variable.
 
    .EXAMPLE
        PS C:\> New-VaultEntry -TargetName 'MyUserCred' -Type 'DomainPassword' -Persist 'Session' -Credential $credential
        Create a new entry in the Credential Manager vault with a custom type
        and persist options. Check the description for detailed information
        about the types and persist locations.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function New-VaultEntry
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    [OutputType([SecurityFever.CredentialManager.CredentialEntry])]
    param
    (
        # The entry target name.
        [Parameter(Mandatory = $true)]
        [System.String]
        $TargetName,

        # The entry type.
        [Parameter(Mandatory = $false)]
        [SecurityFever.CredentialManager.CredentialType]
        $Type = 'Generic',

        # The entry persist location.
        [Parameter(Mandatory = $false)]
        [SecurityFever.CredentialManager.CredentialPersist]
        $Persist = 'LocalMachine',

        # The credential object to store in the vault.
        [Parameter(Mandatory = $true, ParameterSetName = 'Credential')]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential,

        # The username to store in the vault. Specify password too.
        [Parameter(Mandatory = $true, ParameterSetName = 'UsernamePassword')]
        [System.String]
        $Username,

        # The password to store in the vault. Specify username too.
        [Parameter(Mandatory = $true, ParameterSetName = 'UsernamePassword')]
        [System.Security.SecureString]
        $Password,

        # Override any existing entry.
        [Parameter(Mandatory = $false)]
        [Switch]
        $Force
    )

    if ((-not $Force.IsPresent) -and ([SecurityFever.CredentialManager.CredentialStore]::ExistCredential($TargetName, $Type)))
    {
        throw "Entry with target name $TargetName and type $Type already exists!"
    }

    if ($PSCmdlet.ParameterSetName -eq 'UsernamePassword')
    {
        $Credential = New-Object -TypeName PSCredential -ArgumentList $Username, $Password
    }

    if ($Force.IsPresent -or $PSCmdlet.ShouldProcess("$TargetName ($Type)", "Create Entry"))
    {
        $credentialEntry = [SecurityFever.CredentialManager.CredentialStore]::CreateCredential($TargetName, $Type, $Persist, $Credential)

        Write-Output $credentialEntry
    }
}

<#
    .SYNOPSIS
        Removes an existing entry in the Windows Credential Manager vault.
 
    .DESCRIPTION
        This cmdlet uses the native unmanaged Win32 api to remove a existing
        entry in the Windows Credential Manager vault.
 
    .INPUTS
        None.
 
    .OUTPUTS
        None.
 
    .EXAMPLE
        PS C:\> Get-VaultEntry -TargetName 'MyUserCred' -Type 'DomainPassword' -Persist 'Session' | Remove-VaultEntry
        Remove the Credential Manager vault entry which was piped to the cmdlet.
 
    .EXAMPLE
        PS C:\> Remove-VaultEntry -TargetName 'MyUserCred' -Type 'DomainPassword'
        Remove the Credential Manager vault entry with the specified target name
        and type.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function Remove-VaultEntry
{
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    [OutputType([void])]
    param
    (
        # The target entry to delete as objects.
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Object')]
        [SecurityFever.CredentialManager.CredentialEntry[]]
        $InputObject,

        # The name of the target entry to delete.
        [Parameter(Mandatory = $true, ParameterSetName = 'Properties')]
        [System.String]
        $TargetName,

        # The type of the target entry to delete.
        [Parameter(Mandatory = $true, ParameterSetName = 'Properties')]
        [SecurityFever.CredentialManager.CredentialType]
        $Type,

        # Force the removal.
        [Parameter(Mandatory = $false)]
        [Switch]
        $Force
    )

    process
    {
        if ($PSCmdlet.ParameterSetName -eq 'Properties')
        {
            [SecurityFever.CredentialManager.CredentialStore]::RemoveCredential($TargetName, $Type)
        }
        else
        {
            foreach ($object in $InputObject)
            {
                if ($Force.IsPresent -or $PSCmdlet.ShouldProcess("$($object.TargetName) ($($object.Type))", "Remove Entry"))
                {
                    [SecurityFever.CredentialManager.CredentialStore]::RemoveCredential($object.TargetName, $object.Type)
                }
            }
        }
    }
}

<#
    .SYNOPSIS
        Update an existing entry in the Windows Credential Manager vault.
 
    .DESCRIPTION
        This cmdlet uses the native unmanaged Win32 api to update an existing
        entry in the Windows Credential Manager vault. Use a entry object or the
        target name and type combination to identity the entry to update.
        It is possible to update the persist location, the credentials, the
        username and the password. If you specify new credentials, new username
        and password will be ignored.
 
    .INPUTS
        None.
 
    .OUTPUTS
        SecurityFever.CredentialManager.CredentialEntry.
 
    .EXAMPLE
        PS C:\> Update-VaultEntry -TargetName 'MyUserCred' -Type 'DomainPassword' -NewCredential $cred
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function Update-VaultEntry
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    [OutputType([SecurityFever.CredentialManager.CredentialEntry])]
    param
    (
        # The target entry to update as objects.
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Object')]
        [SecurityFever.CredentialManager.CredentialEntry[]]
        $InputObject,

        # The name of the target entry to update.
        [Parameter(Mandatory = $true, ParameterSetName = 'Properties')]
        [System.String]
        $TargetName,

        # The type of the target entry to update.
        [Parameter(Mandatory = $true, ParameterSetName = 'Properties')]
        [SecurityFever.CredentialManager.CredentialType]
        $Type,

        # The entry persist location.
        [Parameter(Mandatory = $false)]
        [AllowNull()]
        [SecurityFever.CredentialManager.CredentialPersist]
        $NewPersist,

        # The credential object to store in the vault.
        [Parameter(Mandatory = $false)]
        [AllowNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $NewCredential,

        # The username to store in the vault. Specify password too.
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [System.String]
        $NewUsername,

        # The password to store in the vault. Specify username too.
        [Parameter(Mandatory = $false)]
        [AllowNull()]
        [System.Security.SecureString]
        $NewPassword,

        # Force the update.
        [Parameter(Mandatory = $false)]
        [Switch]
        $Force
    )

    process
    {
        if ($PSCmdlet.ParameterSetName -eq 'Properties')
        {
            $InputObject = [SecurityFever.CredentialManager.CredentialStore]::GetCredential($TargetName, $Type)
        }

        foreach ($object in $InputObject)
        {
            if (([SecurityFever.CredentialManager.CredentialStore]::ExistCredential($object.TargetName, $object.Type)))
            {
                # If no new persist was specified, use the current value.
                if (-not $PSBoundParameters.ContainsKey('NewPersist'))
                {
                    $NewPersist = $object.Persist
                }

                # If no new credentials were specified, check if the username or
                # password was updated and create a new credentials object.
                if (-not $PSBoundParameters.ContainsKey('NewCredential'))
                {
                    $NewCredential = $object.Credential

                    if ($PSBoundParameters.ContainsKey('NewUsername'))
                    {
                        $NewCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $NewUsername, $NewCredential.Password
                    }
                    if ($PSBoundParameters.ContainsKey('NewPassword'))
                    {
                        $NewCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $NewCredential.Username, $NewPassword
                    }
                }

                if ($Force.IsPresent -or $PSCmdlet.ShouldProcess("$($object.TargetName) ($($object.Type))", "Update Entry"))
                {
                    # Finally, invoke the NewCredential() method to update the
                    # credential entry.
                    $credentialEntry = [SecurityFever.CredentialManager.CredentialStore]::CreateCredential($object.TargetName, $object.Type, $NewPersist, $NewCredential)

                    Write-Output $credentialEntry
                }
            }
            else
            {
                throw "Entry with target name $($object.TargetName) and type $($object.Type) already exists!"
            }
        }
    }
}


function Invoke-AuditPolGetCategoryAllCsv
{
    [CmdletBinding()]
    param ()

    (auditpol.exe /get /category:* /r) |
        Where-Object { -not [String]::IsNullOrEmpty($_) }
}


function Invoke-AuditPolListSubcategoryAllCsv
{
    [CmdletBinding()]
    param ()

    (auditpol.exe /list /subcategory:* /r) |
        Where-Object { -not [String]::IsNullOrEmpty($_) }
}


function Convert-Base32ToByte
{
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Base32
    )

    # RFC 4648 Base32 alphabet
    $rfc4648 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'

    $bits = ''

    # Convert each Base32 character to the binary value between starting at
    # 00000 for A and ending with 11111 for 7.
    foreach ($char in $Base32.ToUpper().ToCharArray())
    {
        $bits += [Convert]::ToString($rfc4648.IndexOf($char), 2).PadLeft(5, '0')
    }

    # Convert 8 bit chunks to bytes, ignore the last bits.
    for ($i = 0; $i -le ($bits.Length - 8); $i += 8)
    {
        [Byte] [Convert]::ToInt32($bits.Substring($i, 8), 2)
    }
}


function Test-AdministratorRole
{
    [CmdletBinding()]
    param ()

    # Check against the generic administrator role (language neutral).
    $AdministratorRole = [Security.Principal.WindowsBuiltInRole]::Administrator

    # Get the current user identity
    $CurrentWindowsPrincipal = [Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()

    return $CurrentWindowsPrincipal.IsInRole($AdministratorRole)
}


function Convert-EventLogObject
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        $Record,

        [Parameter(Mandatory = $true)]
        [System.Collections.Hashtable]
        $Map
    )

    $activity = New-Object -TypeName PSObject -Property @{
        ComputerName = $Record.MachineName.Split('.')[0]
        TimeCreated  = $Record.TimeCreated
        Id           = $Record.Id
        Activity     = $Map[$Record.Id].Activity
        Event        = $Map[$Record.Id].Event
        Type         = ''
        Reason       = ''
        Username     = ''
        Computer     = ''
        Process      = ''
        Comment      = ''
    }

    $activity.PSTypeNames.Insert(0, 'SecurityFever.ActivityRecord')

    Write-Output $activity
}


function Convert-EventLogObjectId1
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        $Record,

        [Parameter(Mandatory = $true)]
        [System.Collections.Hashtable]
        $Map
    )

    $activity = Convert-EventLogObject -Record $Record -Map $Map

    # Grab record properties
    $recordReason = $Record.Properties[12].Value

    # Set default values
    $activity.Reason = "Unknown ($recordReason)"

    # Populate the awake source
    switch ($recordReason)
    {
        1 { $activity.Reason = 'Power Button' }
        3 { $activity.Reason = 'S4 Doze to Hibernate' }
        5 { $activity.Reason = 'Device - ACPI Lid' }
    }

    Write-Output $activity
}


function Convert-EventLogObjectId1074
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        $Record,

        [Parameter(Mandatory = $true)]
        [System.Collections.Hashtable]
        $Map
    )

    $activity = Convert-EventLogObject -Record $Record -Map $Map

    # Grab record properties
    $recordType     = $Record.Properties[4].Value
    $recordUser     = $Record.Properties[6].Value
    $recordComputer = $Record.Properties[1].Value
    $recordProcess  = $Record.Properties[0].Value
    $recordReason   = $Record.Properties[2].Value
    $recordComment  = $Record.Properties[5].Value

    # Set default values
    $activity.Type     = $recordType
    $activity.Reason   = $recordReason
    $activity.Username = $recordUser
    $activity.Computer = $recordComputer
    $activity.Process  = $recordProcess
    $activity.Comment  = $recordComment

    Write-Output $activity
}


function Convert-EventLogObjectId42
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        $Record,

        [Parameter(Mandatory = $true)]
        [System.Collections.Hashtable]
        $Map
    )

    $activity = Convert-EventLogObject -Record $Record -Map $Map

    # Grab record properties
    $recordReason = $Record.Properties[2].Value

    # Set default values
    $activity.Reason = "Unknown ($recordReason)"

    # Populate the sleep reason
    switch ($recordReason)
    {
        0 { $activity.Reason = 'Button or Lid' }
        2 { $activity.Reason = 'Battery' }
        4 { $activity.Reason = 'Application API' }
        6 { $activity.Reason = 'Hibernate from Sleep - Fixed Timeout' }
        7 { $activity.Reason = 'System Idle' }
    }

    Write-Output $activity
}


function Convert-EventLogObjectId4624
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        $Record,

        [Parameter(Mandatory = $true)]
        [System.Collections.Hashtable]
        $Map
    )

    # Definition: Types
    $typeMap = @{
        '2'  = 'Interactive'
        '3'  = 'Network'
        '4'  = 'Batch'
        '5'  = 'Service'
        '7'  = 'Unlock'
        '8'  = 'NetworkCleartext'
        '9'  = 'NewCredentials'
        '10' = 'RemoteInteractive'
        '11' = 'CachedInteractive'
    }

    $activity = Convert-EventLogObject -Record $Record -Map $Map

    # Grab record properties
    $recordType     = $Record.Properties[8].Value.ToString().Trim()
    $recordUser     = $Record.Properties[6].Value + '\' + $Record.Properties[5].Value
    $recordComputer = $Record.Properties[11].Value
    $recordProcess  = $Record.Properties[9].Value.Trim()
    $recordAuth     = $Record.Properties[10].Value
    $recordAuth2    = $Record.Properties[14].Value

    # Set default values
    $activity.Type         = "Unknown ($recordType)"
    $activity.Username     = $recordUser
    $activity.Computer     = $recordComputer
    $activity.Process      = $recordProcess
    $activity.Comment      = "$recordAuth ($recordAuth2)"

    # Populate the type
    if ($typeMap.ContainsKey($recordType))
    {
        $activity.Type = $typeMap[$recordType]
    }

    # Cleanup comment
    $activity.Comment = $activity.Comment.Replace(' (-)', '')

    Write-Output $activity
}


function Convert-EventLogObjectId4625
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        $Record,

        [Parameter(Mandatory = $true)]
        [System.Collections.Hashtable]
        $Map
    )

    # Definition: Types
    $typeMap = @{
        '2'  = 'Interactive'
        '3'  = 'Network'
        '4'  = 'Batch'
        '5'  = 'Service'
        '7'  = 'Unlock'
        '8'  = 'NetworkCleartext'
        '9'  = 'NewCredentials'
        '10' = 'RemoteInteractive'
        '11' = 'CachedInteractive'
    }

    # Definition: Reasons
    $reasonMap = @{
        0xC0000064 = 'Username does not exist'
        0xC000006A = 'Username is correct but the password is wrong'
        0xC0000234 = 'User is currently locked out'
        0xC0000072 = 'Account is currently disabled'
        0xC000006F = 'User tried to logon outside his day of week or time of day restrictions'
        0xC0000070 = 'Workstation restriction, or authentication policy silo violation'
        0xC0000193 = 'Account expiration'
        0xC0000071 = 'Expired password'
        0xC0000133 = 'Clocks between DC and other computer too far out of sync'
        0xC0000224 = 'User is required to change password at next logon'
        0xC0000225 = 'Evidently a bug in Windows and not a risk'
        0xc000015b = 'The user has not been granted the requested logon type (aka logon right) at this machine'
    }

    $activity = Convert-EventLogObject -Record $Record -Map $Map

    # Grab record properties
    $recordType     = $Record.Properties[10].Value.ToString().Trim()
    $recordUser     = $Record.Properties[6].Value + '\' + $Record.Properties[5].Value
    $recordComputer = $Record.Properties[13].Value
    $recordReason   = $Record.Properties[7].Value
    $recordReason2  = $Record.Properties[9].Value
    $recordProcess  = $Record.Properties[11].Value.Trim()
    $recordAuth     = $Record.Properties[12].Value.ToString().Trim()
    $recordAuth2    = $Record.Properties[15].Value.ToString().Trim()

    # Set default values
    $activity.Type         = "Unknown ($recordType)"
    $activity.Reason       = "$recordReason ($recordReason2)"
    $activity.Username     = $recordUser
    $activity.Computer     = $recordComputer
    $activity.Process      = $recordProcess
    $activity.Comment      = "$recordAuth ($recordAuth2)"

    # Populate the type
    if ($typeMap.ContainsKey($recordType))
    {
        $activity.Type = $typeMap[$recordType]
    }

    # Cleanup comment
    $activity.Comment = $activity.Comment.Replace(' (-)', '')

    # Populate reason
    if ($reasonMap.ContainsKey($recordReason))
    {
        $recordReason = $reasonMap[$recordReason]
    }
    if ($reasonMap.ContainsKey($recordReason2))
    {
        $recordReason2 = $reasonMap[$recordReason2]
    }
    $activity.Reason = "$recordReason ($recordReason2)"

    Write-Output $activity



<#
 
    try
    {
        $reason1 = $reasonMap[$reason1]
    }
    catch { }
 
    try
    {
        $reason2 = $reasonMap[$reason2]
    }
    catch { }
 
    $reason = @()
    if (-not [String]::IsNullOrEmpty($reason1) -and $reason1 -ne '-')
    {
        $reason += $reason1
    }
    if (-not [String]::IsNullOrEmpty($reason2) -and $reason2 -ne '-')
    {
        $reason += $reason2
    }
#4625 = @{ Type = 'Logon'; Log = 'Security'; Event = 'Logon Failed' }# An account failed to log on.
 
    $activity.Detail = $activity.Detail -f $type, $user, $computer, ($reason -join ' / '), $process, $auth
#>

}


function Convert-EventLogObjectId4634
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        $Record,

        [Parameter(Mandatory = $true)]
        [System.Collections.Hashtable]
        $Map
    )

    # Definition: Types
    $typeMap = @{
        '2'  = 'Interactive'
        '3'  = 'Network'
        '4'  = 'Batch'
        '5'  = 'Service'
        '7'  = 'Unlock'
        '8'  = 'NetworkCleartext'
        '9'  = 'NewCredentials'
        '10' = 'RemoteInteractive'
        '11' = 'CachedInteractive'
    }

    $activity = Convert-EventLogObject -Record $Record -Map $Map

    # Grab record properties
    $recordType     = $Record.Properties[4].Value.ToString().Trim()
    $recordUser     = $Record.Properties[2].Value + '\' + $Record.Properties[1].Value

    # Set default values
    $activity.Type         = "Unknown ($recordType)"
    $activity.Username     = $recordUser

    # Populate the type
    if ($typeMap.ContainsKey($recordType))
    {
        $activity.Type = $typeMap[$recordType]
    }

    Write-Output $activity
}


function Convert-EventLogObjectId4647
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        $Record,

        [Parameter(Mandatory = $true)]
        [System.Collections.Hashtable]
        $Map
    )

    $activity = Convert-EventLogObject -Record $Record -Map $Map

    # Grab record properties
    $recordUser     = $Record.Properties[2].Value + '\' + $Record.Properties[1].Value

    # Set default values
    $activity.Username     = $recordUser

    Write-Output $activity
}


function Convert-EventLogObjectId6008
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        $Record,

        [Parameter(Mandatory = $true)]
        [System.Collections.Hashtable]
        $Map
    )

    $activity = Convert-EventLogObject -Record $Record -Map $Map

    try
    {
        $lrMark = [char]8206

        $time = $Record.Properties[0].Value
        $date = $Record.Properties[1].Value.Replace("$lrMark", "")

        $activity.TimeCreated = [DateTime]::Parse("$date $time")
    }
    catch
    {
        Write-Warning "TimeCreated value parsing error for event 6008: $_"
    }

    Write-Output $activity
}


function Initialize-ImpersonationContext
{
    [CmdletBinding()]
    param ()

    # Add Win32 native API methods to call to LogonUser()
    if (-not ([System.Management.Automation.PSTypeName]'Win32.AdvApi32').Type)
    {
        Add-Type -Namespace 'Win32' -Name 'AdvApi32' -MemberDefinition '
            [DllImport("advapi32.dll", SetLastError = true)]
            public static extern bool LogonUser(string lpszUserName, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken);
        '

    }

    # Add Win32 native API methods to call to CloseHandle()
    if (-not ([System.Management.Automation.PSTypeName]'Win32.Kernel32').Type)
    {
        Add-Type -Namespace 'Win32' -Name 'Kernel32' -MemberDefinition '
            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern bool CloseHandle(IntPtr handle);
        '

    }

    # Define enumeration for the logon type
    if (-not ([System.Management.Automation.PSTypeName]'Win33.Logon32Type').Type)
    {
        Add-Type -TypeDefinition '
            namespace Win32
            {
                public enum Logon32Type
                {
                    Interactive = 2,
                    Network = 3,
                    Batch = 4,
                    Service = 5,
                    Unlock = 7,
                    NetworkClearText = 8,
                    NewCredentials = 9
                }
            }
        '

    }

    # Define enumeration for the logon provider
    if (-not ([System.Management.Automation.PSTypeName]'Win33.Logon32Type').Type)
    {
        Add-Type -TypeDefinition '
            namespace Win32
            {
                public enum Logon32Provider
                {
                    Default = 0,
                    WinNT40 = 2,
                    WinNT50 = 3
                }
            }
        '

    }

    # Global variable to hold the impersonation context
    if ($null -eq $Script:ImpersonationContext)
    {
        $Script:ImpersonationContext = New-Object -TypeName 'System.Collections.Generic.Stack[System.Security.Principal.WindowsImpersonationContext]'
    }

    # Global variable to hold the PSReadline preference
    if ($null -ne (Get-Module -Name 'PSReadline') -and $null -eq $Script:PSReadlineHistorySaveStyle)
    {
        $Script:PSReadlineHistorySaveStyle = Get-PSReadlineOption | Select-Object -ExpandProperty 'HistorySaveStyle'
    }
}


New-Variable -Name 'ModulePath' -Value $PSScriptRoot