Search code examples
c#powershelladvapi32

How to use Windows API AuditEnumerateCategories function in PowerShell?


I want get the current Advanced Security Audit Policy using PowerShell. I could use auditpol.exe, but its ouput is different per OS language, which makes it difficult to parse.

The settings are stored in a REG_NONE value in HKEY_Local_Machine\Security\Policy\PolAdtEv. I could try to parse the value with the help of that unofficial structure table. My preferred approach, however, is to use the Windows API function AuditQuerySystemPolicy of advapi32.dll.

With the great help of this article, I created a Type in PowerShell as follows.

$MemberDefinition = @'
[DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool AuditEnumerateCategories(
        out IntPtr ppAuditCategoriesArray, 
        out uint pCountReturned);

[DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool AuditLookupCategoryName(
        ref Guid pAuditCategoryGuid, 
        out StringBuilder ppszCategoryName);

[DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool AuditEnumerateSubCategories(
        ref Guid pAuditCategoryGuid, 
        bool bRetrieveAllSubCategories, 
        out IntPtr ppAuditSubCategoriesArray, 
        out uint pCountReturned);

[DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool AuditLookupSubCategoryName(
        ref Guid pAuditSubCategoryGuid, 
        out StringBuilder ppszSubCategoryName);

[DllImport("advapi32.dll")]
    public static extern void AuditFree(
        IntPtr buffer);

[DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool AuditQuerySystemPolicy(
        Guid pSubCategoryGuids, 
        uint PolicyCount, 
        out IntPtr ppAuditPolicy);
'@

$Advapi32 = Add-Type -MemberDefinition $MemberDefinition -Name 'Advapi32' -Namespace 'Win32' -UsingNamespace System.Text -PassThru

The Type is created successfuly, but I fail with the first step - to get the GUIDs of all audit categories. I have issues with the different types. I tried for example

$guid = [Guid].MakeByRefType()
$count = [IntPtr]::Zero
[Win32.Advapi32]::AuditEnumerateCategories([ref]$guid, [ref]$count)
# Exception calling "AuditEnumerateCategories" with 2 Arguments: "The value "System.Guid&" of the type "System.RuntimeType" cannot be coverted to "System.IntPtr".

I also tried to change the AuditEnumerateCategories definition output from IntPtr to Guid. The output of ppAuditCategoriesArray is

A pointer to a single buffer that contains both an array of pointers to GUID structures and the structures themselves.

Unfortunately I have no idea how to handle that in PowerShell.


Solution

  • Take up a Marshal.PtrToStructure Method (.NET). For instance, PtrToStructure(IntPtr, Type) marshals data from an unmanaged block of memory to a newly allocated managed object of the specified type, see the following commented code snippet:

    ### (unchanged)
    ### $MemberDefinition =  …
    ### $Advapi32 = Add-Type … 
    
    $guid  = [System.Int64]::MinValue            ### or ### [System.Int64]0
    $count = [System.Int32]::MaxValue            ### or ### [System.Int64]0
    
    $aux = [Win32.Advapi32]::AuditEnumerateCategories([ref]$guid, [ref]$count)
    
    $guidArr = @()                               ### array to store GUIDs
    
    $guidPtr = [int64]$guid                      ### pointer to next GUID
    $guidOff = [System.Runtime.InteropServices.Marshal]::SizeOf([type][guid])
    $ppszCategoryName = [System.Text.StringBuilder]::new()
    
    for ( $i=0; $i -lt $count; $i++ ) {
        $guidAux = [System.Runtime.InteropServices.Marshal]::
                          PtrToStructure( [System.IntPtr]$guidPtr,[type][guid] )
        $guidArr += $guidAux           ### update array of GUIDs for future use
        $guidPtr += $guidOff           ### shift pointer to next GUID
    
        ### debugging: try another method of [Win32.Advapi32] Runtime Type 
        $foo = [Win32.Advapi32]::AuditLookupCategoryName([ref]$guidAux,[ref]$ppszCategoryName)
        ### debugging: show partial results
        Write-Host $('{0} {1,4} {2,4}' -f $guidAux.Guid, 
                          $ppszCategoryName.Capacity, $ppszCategoryName.Length)
    }