'StringBuilder in inlined C# in PowerShell (2.0 and above)

The C# I am trying to inline is at the bottom, but my error is occurring at the second DllImport, because StringBuilder is not loaded. I have tried a number of things with regards to loading assemblies, adding Using references in the C#, etc. This being one example. But nothing is working and I am pretty sure I am just missing something very obvious to C# programmers, and totally non obvious to my brain.

$assemblies = ('mscorlib.dll')

$code = @'
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
internal static extern IntPtr LoadLibrary(string lpLibFileName);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
internal static extern int LoadString(IntPtr hInstance, uint wID, StringBuilder lpBuffer, int nBufferMax);

public static class PxTest
{
    // Note
}
'@

Add-Type -memberDefinition $code -referencedAssemblies $assemblies -namespace Px -name Test -errorAction stop

I have also tried $assemblies = ('system.text') which then tries to load system.text.dll that isn't found. Which is what sent me to the dll above, which is where StringBuilder is found I believe.

C# code to ultimately use

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
    internal static extern IntPtr LoadLibrary(string lpLibFileName);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
    internal static extern int LoadString(IntPtr hInstance, uint wID, StringBuilder lpBuffer, int nBufferMax);

    public static bool PinUnpinTaskbar(string filePath, bool pin)
    {
        if (!File.Exists(filePath)) throw new FileNotFoundException(filePath);
        int MAX_PATH = 255;
        var actionIndex = pin ? 5386 : 5387; // 5386 is the DLL index for"Pin to Tas&kbar", ref. http://www.win7dll.info/shell32_dll.html
        StringBuilder szPinToStartLocalized = new StringBuilder(MAX_PATH);
        IntPtr hShell32 = LoadLibrary("Shell32.dll");
        LoadString(hShell32, (uint)actionIndex, szPinToStartLocalized, MAX_PATH);
        string localizedVerb = szPinToStartLocalized.ToString();

        string path = Path.GetDirectoryName(filePath);
        string fileName = Path.GetFileName(filePath);

        // create the shell application object
        dynamic shellApplication = Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
        dynamic directory = shellApplication.NameSpace(path);
        dynamic link = directory.ParseName(fileName);

        dynamic verbs = link.Verbs();
        for (int i = 0; i < verbs.Count(); i++)
        {
            dynamic verb = verbs.Item(i);
            if (verb.Name.Equals(localizedVerb))
            {
                verb.DoIt();
                return true;
            }
        }
        return false;
    }

EDIT: Trying to understand the edits. I get the added tags, but replacing some body text with... the same text? That I don't get. And that's at least what I am seeing. StringBuilder is is replaced with StringBuilder is, etc. huh?



Solution 1:[1]

It is because StringBuilder is not fully qualified and / or the namespace System.Text is not used

So either change the parameter to

System.Text.StringBuilder lpBuffer

or add the UsingNamespace parameter to the Add-Type cmdlet

Add-Type -memberDefinition $code -referencedAssemblies $assemblies -namespace Px -UsingNamespace 'System.Text' -name Test -errorAction stop

As your environment is fixed to PoSh 2.0 and Win7 I rewrote the code to handle the dynamic stuff via PowerShell (which is anyway more idiomatic in this case) and to get the localized verb via the C# code you provided so finally it looks like this which provides you a convenient PowerShell function PinUnpinTaskbar

$assemblies = ('mscorlib.dll')
$code = @'
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
    internal static extern IntPtr LoadLibrary(string lpLibFileName);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
    internal static extern int LoadString(IntPtr hInstance, uint wID, StringBuilder lpBuffer, int nBufferMax);

    public static string GetLocalizedPinToStartVerb(bool pin)
    {
        int MAX_PATH = 255;
        int actionIndex = pin ? 5386 : 5387; // 5386 is the DLL index for"Pin to Tas&kbar", ref. http://www.win7dll.info/shell32_dll.html
        StringBuilder szPinToStartLocalized = new StringBuilder(MAX_PATH);
        IntPtr hShell32 = LoadLibrary("Shell32.dll");
        LoadString(hShell32, (uint)actionIndex, szPinToStartLocalized, MAX_PATH);
        return szPinToStartLocalized.ToString();
    }
'@

Add-Type -MemberDefinition $code `
         -ReferencedAssemblies $assemblies `
         -Namespace Px `
         -UsingNamespace 'System.Text' `
         -Name Helper `
         -ErrorAction stop

function PinUnpinTaskbar([string]$FilePath, [boolean]$Pin) {
    if (Test-Path $FilePath) {
        $localizedVerb = [Px.Helper]::GetLocalizedPinToStartVerb($Pin)

        $path = [IO.Path]::GetDirectoryName($FilePath)
        $fileName = [IO.Path]::GetFileName($FilePath)

        $shellAppType = [Type]::GetTypeFromProgID("Shell.Application")
        $shellAppInst = [Activator]::CreateInstance($shellAppType)
        $directory = $shellAppInst.NameSpace($path)
        $link = $directory.ParseName($fileName)
        $verbs = $link.Verbs()
        for ($i = 0; $i -lt $verbs.Count; ++$i) {
            $verb = $verbs.Item($i)
            Write-Host $verb.Name
            if ($verb.Name.Equals($localizedVerb)) {
                $verb.DoIt()
                return $true
            }
        }
        return $false
    }
    else {
        Write-Error "File '$FilePath' does not exist"
    }
}  

Script in action on a vanilla Win7 machine

Showing script in action

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1