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
    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
    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
    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
        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

    # Get the global impersonation context
    $globalImpersonationContext = Get-Variable -Name 'ImpersonationContext' -Scope 'Global'

    if ($globalImpersonationContext.Count -gt 0)
    {
        # Get the latest impersonation context
        $impersonationContext = $globalImpersonationContext.Pop()

        # Undo the impersonation
        $impersonationContext.Undo()
    }
}

<#
    .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"
    }

    # Now, impersonate the new user account
    $impersonationContext = [System.Security.Principal.WindowsIdentity]::Impersonate($tokenHandle)
    $globalImpersonationContext = Get-Variable -Name 'ImpersonationContext' -Scope 'Global'
    $globalImpersonationContext.Push($impersonationContext)

    # 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 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-VaultEntryCredential cmdlet or you can get a secure string
    with the Get-VaultEntrySecureString 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 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-VaultEntryCredential -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-VaultEntryCredential
{
    [CmdletBinding()]
    [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 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-VaultEntrySecureString -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-VaultEntrySecureString
{
    [CmdletBinding()]
    [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:\>

    .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 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
                }
            }
        '

    }

    # Get the global impersonation context
    $globalImpersonationContext = Get-Variable -Name 'ImpersonationContext' -Scope 'Global'

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

        New-Variable -Name 'ImpersonationContext' -Value $stack -Option ReadOnly -Scope Global -Force
    }
}


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