[Note] This is a rather old article from 2014. The world and PowerShell have changed. For an even easier way than presented here see the comment at the bottom of the article.
In this post I present a quick refresher on how to make C# extension methods available to objects and classes in PowerShell sessions.
C# allows you to extend classes with methods you define yourself via Extension Methods. This means you can add methods to arbitrary classes which you do not have written yourself or do not own the code for it and then call these methods as if they were part of the class. Unfortunately PowerShell does not really understand this concept but provides a workaround towards this.
You will find that just importing your extension method via Add-Type will not be sufficient to call the method, in our case ‘theAnswer()’:
PS > [string] $s = "tralala"; PS > $TypeDefinition = @" using System; namespace biz.dfch.CS { public static class StringExtension { public static int theAnswer(this System.String s) { return 42; } } } "@ PS > Add-Type $TypeDefinition; PS > $s.theAnswer(); Method invocation failed because [System.String] doesn't contain a method named 'theAnswer'. At line:1 char:1 + [string]::theAnswer() + ~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : MethodNotFound
To get this actually working, you additional have to include this method in the PowerShell defined types via Update-TypeData:
PS > $TypeData = @" <?xml version="1.0" encoding="utf-8" ?> <Types> <Type> <Name>System.String</Name> <Members> <ScriptMethod> <Name>theAnswer</Name> <Script> [biz.dfch.CS.StringExtension]::theAnswer(`$this) </Script> </ScriptMethod> </Members> </Type> </Types> "@ PS > $PowerShellTypesFileAndPath = '{0}.ps1xml' -f [System.IO.Path]::GetTempFileName(); PS > $TypeData | Out-File $PowerShellTypesFileAndPath -Encoding default; PS > Update-TypeData $PowerShellTypesFileAndPath; PS > Remove-Item $PowerShellTypesFileAndPath; PS > $s.theAnswer(); 42
So now everything works just as expected, except that our method shows up as a ‘script method’ (as we imported it via ‘Update-TypeData’):
PS > # theAnswer is shown as e (non-static) script method PS > $s | gm -Name theAnswer; TypeName: System.String Name MemberType Definition ---- ---------- ---------- theAnswer ScriptMethod System.Object theAnswer();
There is a very basic way to extend types without that much hassle.
Update-TypeData -TypeName -MemberType ScriptMethod -MemberName -Value { [class]::Method($vars,…) OR just streight powershell… }
This can be placed in your PS Profile if you wish to retain it in each session.
Heres a more simple example of yours above:
Update-TypeData -TypeName System.String -MemberType ScriptMethod -MemberName theAnswer -Value { return 42 }
Here is a really nice example extending strings to encrypting itself where it can only be decrypted by the user that created it and is transferable between sessions (i.e. you save it to a file and read it later – great for storing plain text passwords when basic authentication is the only offered authentication protocol):
Add-Type -AssemblyName System.Security
$encrypt = {
$bytes = [System.Text.Encoding]::UTF8.GetBytes($this)
$pbytes = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)
return [Convert]::ToBase64String($pbytes)
}
$decrypt = {
$pbytes = [Convert]::FromBase64String($this)
$bytes = [System.Security.Cryptography.ProtectedData]::Unprotect($pbytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)
return [System.Text.Encoding]::UTF8.GetString($bytes)
}
Update-TypeData -TypeName System.String -MemberType ScriptMethod -MemberName Encrypt -Value $encrypt
Update-TypeData -TypeName System.String -MemberType ScriptMethod -MemberName Decrypt -Value $decrypt
#Save the password to a file
“supersecrectpassword”.Encrypt() | Out-File C:\temp\password.txt
#Open a new session and read in the file
$password = (Get-Content -Path C:\temp\password.txt -Raw).Decrypt()
> supersecrectpassword
#Open a new session as a different user and fails to decrypt
$password = (Get-Content -Path C:\temp\password.txt -Raw).Decrypt()
> Exception calling “Decrypt” with “0” argument(s): “Exception calling “Unprotect” with “3” argument(s): “Key not valid
for use in specified state.
“”
At line:1 char:1
+ $password = (Get-Content -Path C:\temp\password.txt -Raw).Decrypt()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ScriptMethodRuntimeException
Hi Steven, you are right – this looks much cleaner and easier. The original article of mine dates back to 2014 where this was not yet possible as far as I remember. So thank you for your hint! I added a remark at the top of my article pointing to your comment so hopefully more people can benefit from it. Regards, Ronald