DSCResources/AuditPolicyResourceHelper/AuditPolicyResourceHelper.psm1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
#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' )