DSCResources/AuditPolicyResourceHelper/AuditPolicyResourceHelper.psm1
#Requires -Version 4.0 <# This PS module contains functions for Desired State Configuration (DSC) AuditPolicyDsc provider. It enables querying, creation, removal and update of Windows advanced audit policies through Get, Set, and Test operations on DSC managed nodes. #> <# .SYNOPSIS Retrieves the localized string data based on the machine's culture. Falls back to en-US strings if the machine's culture is not supported. .PARAMETER ResourceName The name of the resource as it appears before '.strings.psd1' of the localized string file. For example: AuditPolicySubcategory: MSFT_AuditPolicySubcategory AuditPolicyOption: MSFT_AuditPolicyOption #> function Get-LocalizedData { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ParameterSetName = 'resource')] [ValidateNotNullOrEmpty()] [String] $ResourceName, [Parameter(Mandatory = $true, ParameterSetName = 'helper')] [ValidateNotNullOrEmpty()] [String] $HelperName ) # With the helper module just update the name and path variables as if it were a resource. if ($PSCmdlet.ParameterSetName -eq 'helper') { $resourceDirectory = $PSScriptRoot $ResourceName = $HelperName } else { # Step up one additional level to build the correct path to the resource culture. $resourceDirectory = Join-Path -Path ( Split-Path $PSScriptRoot -Parent ) ` -ChildPath $ResourceName } $localizedStringFileLocation = Join-Path -Path $resourceDirectory -ChildPath $PSUICulture if (-not (Test-Path -Path $localizedStringFileLocation)) { # Fallback to en-US $localizedStringFileLocation = Join-Path -Path $resourceDirectory -ChildPath 'en-US' } Import-LocalizedData ` -BindingVariable 'localizedData' ` -FileName "$ResourceName.strings.psd1" ` -BaseDirectory $localizedStringFileLocation return $localizedData } <# .SYNOPSIS Invoke-AuditPol is a private function that wraps auditpol.exe providing a centralized function to manage access to and the output of auditpol.exe. .DESCRIPTION The function will accept a string to pass to auditpol.exe for execution. Any 'get' or 'set' opertions can be passed to the central wrapper to execute. All of the nuances of auditpol.exe can be further broken out into specalized functions that call Invoke-AuditPol. Since the call operator is being used to run auditpol, the input is restricted to only execute against auditpol.exe. Any input that is an invalid flag or parameter in auditpol.exe will return an error to prevent abuse of the call. The call operator will not parse the parameters, so they are split in the function. .PARAMETER Command The action that audtipol should take on the subcommand. .PARAMETER SubCommand The subcommand to execute. .OUTPUTS The raw string output of auditpol.exe with the /r switch to return a CSV string. .EXAMPLE Invoke-AuditPol -Command 'Get' -SubCommand 'Subcategory:Logon' #> function Invoke-AuditPol { [OutputType([Object])] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateSet('Get', 'Set', 'List', 'Restore', 'Backup')] [String] $Command, [Parameter(Mandatory = $true)] [String[]] $SubCommand ) # Localized messages for Write-Verbose statements in this resource $localizedData = Get-LocalizedData -HelperName 'AuditPolicyResourceHelper' <# The raw auditpol data with the /r switch is a 3 line CSV 0 - header row 1 - blank row 2 - the data row we are interested in #> # set the base commands to execute if ( $Command -eq 'Get') { $auditpolArguments = @("/$Command", "/$SubCommand", "/r" ) } else { # The set subcommand comes in an array of the subcategory and flag $auditpolArguments = @("/$Command", "/$($SubCommand[0])", $SubCommand[1] ) } Write-Debug -Message ( $localizedData.ExecuteAuditpolCommand -f $auditpolArguments ) try { # Use System.Diagnostics.Process to process the auditpol command $process = New-Object System.Diagnostics.Process $process.StartInfo.Arguments = $auditpolArguments $process.StartInfo.CreateNoWindow = $true $process.StartInfo.FileName = 'auditpol.exe' $process.StartInfo.RedirectStandardOutput = $true $process.StartInfo.UseShellExecute = $false $null = $process.Start() $auditpolReturn = $process.StandardOutput.ReadToEnd() # auditpol does not throw exceptions, so test the results and throw if needed if ( $process.ExitCode -ne 0 ) { throw New-Object System.ArgumentException } if ( $Command -notmatch "Restore|Backup" ) { return ( ConvertFrom-Csv -InputObject $auditpolReturn ) } } catch [System.ComponentModel.Win32Exception] { # Catch error if the auditpol command is not found on the system Write-Error -Message $localizedData.AuditpolNotFound } catch [System.ArgumentException] { # Catch the error thrown if the lastexitcode is not 0 [String] $errorString = $error[0].Exception $errorString = $errorString + "`$LASTEXITCODE = $LASTEXITCODE;" $errorString = $errorString + " Command = auditpol $commandString" Write-Error -Message $errorString } catch { # Catch any other errors Write-Error -Message ( $localizedData.UnknownError -f $error[0] ) } } <# .SYNOPSIS Get-FixedLanguageAuditCSV is a private function that returns the contents of a CSV with US-English names, even if input is not US English .PARAMETER Path The path to stage the auditCSV to (defaulted to the temp directory). FILE MUST EXIST AND BE A VALID AUDIT.CSV FILE, AND THE FIRST LINE MUST BE THE CSV HEADER. .OUTPUTS Content from the file converted from CSV to custom objects with US-English property names. .EXAMPLE $csv = Get-StagedAuditPolicy #> function Get-FixedLanguageAuditCSV { [OutputType([System.Object[]])] [CmdletBinding()] param ( [Parameter()] [String] $Path = $(Join-Path -Path $env:Temp -ChildPath "audit.csv") ) $headerSet = "Machine Name", "Policy Target", "Subcategory", "Subcategory GUID", "Inclusion Setting", "Exclusion Setting", "Setting Value" return (Get-Content -Path $Path | Select-Object -Skip 1 | ConvertFrom-Csv -Header $headerSet) } <# .SYNOPSIS Get-StagedAuditPol is a private function that creates staged AuditPolicy CSVs for usage over several resource blocks .DESCRIPTION The function tests if a staged AuditPolicyCSV is available and not out of date. If needed, it creates a new staged CSV .PARAMETER Path The path to stage the auditCSV to (defaulted to the temp directory) .OUTPUTS The current or newly created Staged CSV. .EXAMPLE $csv = Get-StagedAuditPolicy #> function Get-StagedAuditPolicyCSV { [OutputType([System.Object[]])] [CmdletBinding()] param ( [Parameter()] [String] $Path = $(Join-Path -Path $env:Temp -ChildPath "audit.csv") ) # Localized messages for Write-Verbose statements in this resource $localizedData = Get-LocalizedData -HelperName 'AuditPolicyResourceHelper' $auditCSV = Get-Item -Path $Path -ErrorAction SilentlyContinue if ($null -ne $auditCSV) { # Determine if the CSV was created more than 5 minutes ago, if it was, delete it and create a new one. if ((New-TimeSpan -Start $auditCSV.CreationTime -End ([datetime]::Now)).Minutes -ge 5) { Write-Debug -Message ( $localizedData.AuditCSVOutdated -f $path, $auditCSV.CreationTime) Try { Remove-Item -Path $auditCSV -Force Write-Debug -Message ( $localizedData.AuditCSVDeleted -f $Path) } Catch { Write-Debug -Message ( $localizedData.AuditCSVLocked -f $Path) Continue } } else { Write-Debug -Message ( $localizedData.AuditCSVLocked -f $Path) return Get-FixedLanguageAuditCSV -Path $auditCSV } } Write-Debug -Message ( $localizedData.AuditCSVNotFound -f $Path) Invoke-AuditPol -Command "Backup" -SubCommand "file:$Path" Write-Debug -Message ( $localizedData.AuditCSVCreated -f $auditCSV ) if (!(Test-Path -Path $Path)) { $inf = [System.Management.Automation.ItemNotFoundException]::new( ($localizedData.FileNotFound -f $Path), $fnf) Throw $inf } $auditCSV = Get-Item -Path $Path return Get-FixedLanguageAuditCSV $auditCSV } # Static Name translation to ensure GUIDS are the source of truth. ### Hash table to map audit subcategory names (US English) to GUIDs $AuditSubcategoryToGUIDHash = @{ ###### Edited from "auditpol /list /subcategory:* /v" ###### ######Category/Subcategory GUID ######System = "69979848-797A-11D9-BED3-505054503030"; "Security State Change" = "0CCE9210-69AE-11D9-BED3-505054503030"; "Security System Extension" = "0CCE9211-69AE-11D9-BED3-505054503030"; "System Integrity" = "0CCE9212-69AE-11D9-BED3-505054503030"; "IPsec Driver" = "0CCE9213-69AE-11D9-BED3-505054503030"; "Other System Events" = "0CCE9214-69AE-11D9-BED3-505054503030"; ######Logon/Logoff = "69979849-797A-11D9-BED3-505054503030"; "Logon" = "0CCE9215-69AE-11D9-BED3-505054503030"; "Logoff" = "0CCE9216-69AE-11D9-BED3-505054503030"; "Account Lockout" = "0CCE9217-69AE-11D9-BED3-505054503030"; "IPsec Main Mode" = "0CCE9218-69AE-11D9-BED3-505054503030"; "IPsec Quick Mode" = "0CCE9219-69AE-11D9-BED3-505054503030"; "IPsec Extended Mode" = "0CCE921A-69AE-11D9-BED3-505054503030"; "Special Logon" = "0CCE921B-69AE-11D9-BED3-505054503030"; "Other Logon/Logoff Events" = "0CCE921C-69AE-11D9-BED3-505054503030"; "Network Policy Server" = "0CCE9243-69AE-11D9-BED3-505054503030"; "User / Device Claims" = "0CCE9247-69AE-11D9-BED3-505054503030"; "Group Membership" = "0CCE9249-69AE-11D9-BED3-505054503030"; ######Object Access = "6997984A-797A-11D9-BED3-505054503030"; "File System" = "0CCE921D-69AE-11D9-BED3-505054503030"; "Registry" = "0CCE921E-69AE-11D9-BED3-505054503030"; "Kernel Object" = "0CCE921F-69AE-11D9-BED3-505054503030"; "SAM" = "0CCE9220-69AE-11D9-BED3-505054503030"; "Certification Services" = "0CCE9221-69AE-11D9-BED3-505054503030"; "Application Generated" = "0CCE9222-69AE-11D9-BED3-505054503030"; "Handle Manipulation" = "0CCE9223-69AE-11D9-BED3-505054503030"; "File Share" = "0CCE9224-69AE-11D9-BED3-505054503030"; "Filtering Platform Packet Drop" = "0CCE9225-69AE-11D9-BED3-505054503030"; "Filtering Platform Connection" = "0CCE9226-69AE-11D9-BED3-505054503030"; "Other Object Access Events" = "0CCE9227-69AE-11D9-BED3-505054503030"; "Detailed File Share" = "0CCE9244-69AE-11D9-BED3-505054503030"; "Removable Storage" = "0CCE9245-69AE-11D9-BED3-505054503030"; "Central Policy Staging" = "0CCE9246-69AE-11D9-BED3-505054503030"; ######Privilege Use = "6997984B-797A-11D9-BED3-505054503030"; "Sensitive Privilege Use" = "0CCE9228-69AE-11D9-BED3-505054503030"; "Non Sensitive Privilege Use" = "0CCE9229-69AE-11D9-BED3-505054503030"; "Other Privilege Use Events" = "0CCE922A-69AE-11D9-BED3-505054503030"; ######Detailed Tracking = "6997984C-797A-11D9-BED3-505054503030"; "Process Creation" = "0CCE922B-69AE-11D9-BED3-505054503030"; "Process Termination" = "0CCE922C-69AE-11D9-BED3-505054503030"; "DPAPI Activity" = "0CCE922D-69AE-11D9-BED3-505054503030"; "RPC Events" = "0CCE922E-69AE-11D9-BED3-505054503030"; "Plug and Play Events" = "0CCE9248-69AE-11D9-BED3-505054503030"; "Token Right Adjusted Events" = "0CCE924A-69AE-11D9-BED3-505054503030"; ######Policy Change = "6997984D-797A-11D9-BED3-505054503030"; "Audit Policy Change" = "0CCE922F-69AE-11D9-BED3-505054503030"; "Authentication Policy Change" = "0CCE9230-69AE-11D9-BED3-505054503030"; "Authorization Policy Change" = "0CCE9231-69AE-11D9-BED3-505054503030"; "MPSSVC Rule-Level Policy Change" = "0CCE9232-69AE-11D9-BED3-505054503030"; "Filtering Platform Policy Change" = "0CCE9233-69AE-11D9-BED3-505054503030"; "Other Policy Change Events" = "0CCE9234-69AE-11D9-BED3-505054503030"; ######Account Management = "6997984E-797A-11D9-BED3-505054503030"; "User Account Management" = "0CCE9235-69AE-11D9-BED3-505054503030"; "Computer Account Management" = "0CCE9236-69AE-11D9-BED3-505054503030"; "Security Group Management" = "0CCE9237-69AE-11D9-BED3-505054503030"; "Distribution Group Management" = "0CCE9238-69AE-11D9-BED3-505054503030"; "Application Group Management" = "0CCE9239-69AE-11D9-BED3-505054503030"; "Other Account Management Events" = "0CCE923A-69AE-11D9-BED3-505054503030"; ######DS Access = "6997984F-797A-11D9-BED3-505054503030"; "Directory Service Access" = "0CCE923B-69AE-11D9-BED3-505054503030"; "Directory Service Changes" = "0CCE923C-69AE-11D9-BED3-505054503030"; "Directory Service Replication" = "0CCE923D-69AE-11D9-BED3-505054503030"; "Detailed Directory Service Replication" = "0CCE923E-69AE-11D9-BED3-505054503030"; ######Account Logon = "69979850-797A-11D9-BED3-505054503030"; "Credential Validation" = "0CCE923F-69AE-11D9-BED3-505054503030"; "Kerberos Service Ticket Operations" = "0CCE9240-69AE-11D9-BED3-505054503030"; "Other Account Logon Events" = "0CCE9241-69AE-11D9-BED3-505054503030"; "Kerberos Authentication Service" = "0CCE9242-69AE-11D9-BED3-505054503030"; } ### Hash table to map GUIDs to audit subcategory names (US English) $AuditGUIDToSubCategoryHash = @{} $AuditSubcategoryToGUIDHash.Keys | ForEach-Object { $AuditGUIDToSubCategoryHash.Add($AuditSubcategoryToGUIDHash[$_], $_) } $AuditFlagToSettingValue = @{ 'No Auditing' = 0 'Success' = 1 'Failure' = 2 'Success and Failure' = 3 } $AuditSettingValueToFlag = @( 'No Auditing', 'Success', 'Failure', 'Success and Failure' ) <# .SYNOPSIS Write-StagedAuditCSV is a private function that writes new values to the Staged Audit.CSV and imports them using auditpol .DESCRIPTION The function takes a new value to write and combines it with the staged csv. .PARAMETER Name The subcategory. .PARAMETER Flag The flag the subcategory should be set to. .PARAMETER Path The path to stage the auditCSV to (defaulted to the temp directory) .OUTPUTS The current or newly created Staged CSV. .EXAMPLE Write-StagedAuditCSV -Name #> function Write-StagedAuditCSV { [OutputType([System.Object[]])] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [GUID] $GUID, [Parameter(Mandatory = $true)] [ValidateRange(0, 4)] [Int] $SettingValue, [Parameter()] [String] $Path = $(Join-Path -Path $env:Temp -ChildPath "audit.csv"), [Parameter(Mandatory = $true)] [ValidateSet("Present", "Absent")] [String] $Ensure ) if ($AuditGUIDToSubCategoryHash.ContainsKey($GUID.Guid)) { $Name = $AuditGUIDToSubCategoryHash[$GUID.Guid] } else { Throw ($localizedData.SubCategoryTranslationFailed -f $GUID) } # translate Present/Absent to inclusion settings $auditState = [pscustomobject]@{ 'Inclusion Setting' = '' 'Exclusion Setting' = '' } $auditFlag = $AuditSettingValueToFlag[$SettingValue] # Localized messages for Write-Verbose statements in this resource $localizedData = Get-LocalizedData -HelperName 'AuditPolicyResourceHelper' $auditCSV = Get-StagedAuditPolicyCSV $currentValue = $auditCSV.Where( {$_.'SubCategory GUID' -eq "{$($GUID.guid)}"}) $currentSetting = 0 if ($null -eq $currentValue) { Write-Debug -Message ($localizedData.CurrentCSVValueMissing -f $GUID) $currentSetting = 0 } else { $currentSetting = $currentValue.'Setting Value' } if ($Ensure -eq "Present") { $auditState.'Inclusion Setting' = $auditFlag } else { switch ($currentSetting) { 0 { # There's no way to set the Absent of No Auditing. # Should it be Success? Failure? Both? return } 1 { if ($currentSetting -eq 1) { # If Success is absent and the current value is Success, the result is "No Auditing" $SettingValue = 0 } elseif ($currentSetting -eq 3) { # If Success is absent and the current value is Succes and Failure, the result is Failure. $SettingValue = 2 } else { $SettingValue = $currentSetting } } 2 { if ($currentSetting -eq 2) { # If Failure is absent and the current value is Failure, the result is "No Auditing" $SettingValue = 0 } elseif ($currentSetting -eq 3) { # If Success is absent and the current value is Succes and Failure, the result is Success. $SettingValue = 1 } else { $SettingValue = $currentSetting } } 3 { # if Success And Failure should be absent, the result is No Auditing $SettingValue = 0 } } $auditState.'Exclusion Setting' = $auditFlag } $auditCSV = $auditCSV | Where-Object {$_.'Subcategory GUID' -ne "{$($GUID.guid)}" -and !([string]::IsNullOrEmpty($_.'Subcategory GUID'))} $tmpValue = [pscustomobject][ordered]@{ 'Machine Name' = $env:ComputerName 'Policy Target' = "System" 'Subcategory' = "Audit $Name" 'SubCategory GUID' = "{$($GUID.guid)}" 'Inclusion Setting' = $auditState.'Inclusion Setting' 'ExclusionSetting' = $auditState.'Exclusion Setting' 'Setting Value' = $SettingValue } $auditCSV += $tmpValue Write-Debug -Message ( $localizedData.WriteStagedCSV -f $Guid, $AuditFlag ) Try { $auditCSV | ConvertTo-Csv -NoTypeInformation | ForEach-Object {$_.Replace('"', '')} | Out-File -FilePath $Path -Force } Catch { Throw ($localizedata.SaveAuditCSVFailure -f $Path) } Invoke-AuditPol -Command Restore -SubCommand "file:$Path" Write-Debug -Message ( $localizedData.RestoredAuditCSV ) } <# .SYNOPSIS Returns the list of valid Subcategories. .DESCRIPTION This funciton will check if the list of valid subcategories has already been created. If the list exists it will simply return it. If it doe not exists, it will generate it and return it. #> function Get-ValidSubcategoryList { [OutputType([String[]])] [CmdletBinding()] param () if ( $null -eq $script:validSubcategoryList ) { $script:validSubcategoryList = @() # Populating $validSubcategoryList uses Invoke-AuditPol and needs to follow the definition. # Populating $validSubcategoryList uses Invoke-AuditPol and needs to follow the definition. $script:validSubcategoryList = Invoke-AuditPol -Command Get -SubCommand "category:*" | Select-Object -Property Subcategory -ExpandProperty Subcategory } return $script:validSubcategoryList } <# .SYNOPSIS Verifies that the Subcategory is valid. .PARAMETER Name The name of the Subcategory to validate. #> function Test-ValidSubcategory { [OutputType([Boolean])] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $Name ) if ( ( Get-ValidSubcategoryList ) -icontains $Name ) { return $true } else { return $false } } Export-ModuleMember -Variable AuditSubCategorytoGUIDHash, AuditGUIDTOSubCategoryHash, AuditFlagToSettingValue, AuditSettingValueToFlag -Function @( 'Invoke-AuditPol', 'Get-LocalizedData', 'Write-StagedAuditCSV', 'Get-StagedAuditPolicyCSV', 'Get-FixedLanguageAuditCSV', 'Test-ValidSubcategory' ) |