Home > Software engineering >  Using Win32 GetStringTypeW() in a PowerShell script
Using Win32 GetStringTypeW() in a PowerShell script

Time:05-25

I'd like to use the Win32 GetStringTypeW() method inside a PowerShell script.

I figured out the correct signature in C# and the following code worked nicely there:

[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern uint GetStringTypeW(uint dwInfoType, string lpSrcStr, int cchSrc, out ushort lpCharType);

private const uint CT_CTYPE1 = 0x0001;

public void MyMethod(string strIn) {
  ushort[] lpCharType = new ushort[strIn.Length];
  GetStringTypeW(CT_CTYPE1, strIn, strIn.Length, out lpCharType[0]);

  // Do stuff with lpCharType
}

The lpCharType array gets filled with 16-bit unsigned integers; one for each character of the string that was passed in. The integers can be checked with bitwise comparisons to find out which types of characters are present in the string.

I translated that C# code to the following in PowerShell:

$MethodDefinition = @'
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
public static extern uint GetStringTypeW(uint dwInfoType, string lpSrcStr, int cchSrc, out ushort lpCharType);
'@

$Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name 'Kernel32' -Namespace 'Win32' -PassThru

[System.UInt32] $CT_CTYPE1 = 0x0001

[System.UInt16[]] $lpCharType = [System.UInt16[]]::new($strIn.Length)

$Kernel32::GetStringTypeW($CT_CTYPE1, $strIn, $strIn.Length, [ref] $lpCharType[0])

# Do stuff with $lpCharType

That just doesn't populate $lpCharType with anything, and depending how I use that code I can also kill PowerShell entirely with a System.AccessViolationException: Attempted to read or write protected memory.

It seems like there's something weird going on in memory which I don't fully understand, so does anyone have any suggestions on how to make it work?

Note: Interestingly, if I try passing in a single UInt16 parameter instead of an array of them, it gets filled with a proper integer value, so the code kind of works, but of course, it can't hold more than one value, and that doesn't solve the Access Violation.

If I have to, I can add a go-between C# method to the $MethodDefinition to accept a string from PowerShell, call GetStringTypeW(), and return the output, but I was hoping to avoid filling my PowerShell script with C# code if possible.

CodePudding user response:

As zett42 points out, in PowerShell you cannot obtain a reference to an individual element of a value-type array, as discussed in this answer.

However, you can simply use an array parameter in your P/Invoke declaration, and pass the array as a whole from PowerShell:

$MethodDefinition = @'
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
public static extern uint GetStringTypeW(uint dwInfoType, 
                          string lpSrcStr, 
                          int cchSrc, 
                          ushort[] lpCharType); // Declared as array, w/o `out`
'@

$Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name 'Kernel32' -Namespace 'Win32' -PassThru

[System.UInt32] $CT_CTYPE1 = 0x0001

$strIn = 'A!'

[uint16[]] $lpCharType = [uint16[]]::new($strIn.Length)

$ok = $Kernel32::GetStringTypeW($CT_CTYPE1, $strIn, -1, $lpCharType)

$lpCharType # -> 897, 528
  • Related