.netpowershellerror-handlingpinvokegetlasterror

Powershell, PInvoke and GetLastError


I'm trying to use GetFileSecurity from Powershell via PInvoke. The call itself works, but both System.Runtime.InteropServices.Marshal.GetLastWin32Error and GetLastError (which I imported for testing) return the wrong error code. It seems that SetLastError = true in the PInvoke signature doesn't have any effect at all, since the first time the script is run after starting a new Powershell instance, both return 203 (ERROR_ENVVAR_NOT_FOUND), but if I call the same script again, both return 0.

As a quick test, I whipped up an equivalent C program, and GetLastError returns the expected value (122 == ERROR_INSUFFICIENT_BUFFER) there.

Note that I'm new to Powershell and .NET in general, so please excuse any obvious faux pas.

$signature = @'
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetFileSecurity(
  string lpFileName,
  int RequestedInformation,
  byte [] pSecurityDescriptor,
  uint nLength,
  out int lpnLengthNeeded
);

[DllImport("Kernel32.dll")]
public static extern uint GetLastError();
'@

$enum = @'
namespace Win32 {
  public enum SECURITY_INFORMATION : uint
  {
    OWNER_SECURITY_INFORMATION        = 0x00000001,
    GROUP_SECURITY_INFORMATION        = 0x00000002,
    DACL_SECURITY_INFORMATION         = 0x00000004,
    SACL_SECURITY_INFORMATION         = 0x00000008,
    UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000,
    UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000,
    PROTECTED_SACL_SECURITY_INFORMATION   = 0x40000000,
    PROTECTED_DACL_SECURITY_INFORMATION   = 0x80000000
  }
}
'@


Add-Type -TypeDefinition $enum
Add-Type -MemberDefinition $signature -Name API -Namespace Win32

$len = 0
$sec = New-Object byte[] 0
$info = new-object Win32.SECURITY_INFORMATION
$info = [Win32.SECURITY_INFORMATION]::OWNER_SECURITY_INFORMATION -bor
        [Win32.SECURITY_INFORMATION]::GROUP_SECURITY_INFORMATION -bor
        [Win32.SECURITY_INFORMATION]::DACL_SECURITY_INFORMATION
[Win32.API]::GetFileSecurity("c:\temp", $info, $sec, $sec.Length, [ref] $len)
[System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
[Win32.API]::GetLastError()

Solution

  • Rather that directly pinvoking, I would create a wrapper C# class that makes the GetFileSecurity call and then gets the error. It is possible that any calls PowerShell makes to Win32 API between your GetFileSecurity() pinvoke call and the GetLastWin32Error() pinvoke call is resetting the Win32 error number.