Imagine a situation when you want to write some C# code but you don’t have a C# compiler nearby. You might want to use online IDE like Ideone or .NET Fiddle to write and execute piece of code, but these environments are very restricted: you cannot load arbitrary libraries, you cannot access files, you have a time limit for execution. If you have PowerShell installed (which is by default since Windows 7) then you can use it to compile and execute C# code. Let’s see how to do that.
Table of Contents
Basic case
We can load any type using Add-Type commandlet. Basic invocation requires type definition and language:
Add-Type -TypeDefinition 'your code here' -Language CSharp
For instance, to load and execute hello world use this script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$code = @" using System; namespace HelloWorld { public class Program { public static void Main(){ Console.WriteLine("Hello world!"); } } } "@ Add-Type -TypeDefinition $code -Language CSharp iex "[HelloWorld.Program]::Main()" |
Save this code to file “Hello.ps1”, start PowerShell, change directory, and run command .\Hello.ps1
. You should see “Hello world!” in the console. Congratulations! You just compiled and executed your first C# program in PowerShell.
Multiple executions
If you modify the code and try to run this script again, you will get the following error:
1 2 3 4 5 6 |
Add-Type : Cannot add type. The type name 'HelloWorld.Program' already exists. At Z:\Hello.ps1:14 char:1 + Add-Type -TypeDefinition $code -Language CSharp + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (HelloWorld.Program:String) [Add-Type], Exception + FullyQualifiedErrorId : TYPE_ALREADY_EXISTS,Microsoft.PowerShell.Commands.AddTypeCommand |
This happens because you cannot load type into the same App Domain again, unfortunately, you cannot unload type. If you try to google solution, you will probably land on this SO question. All these solutions are cumbersome and hard to remember. Also starting nested PowerShell might be very slow if you have complicated logic in your user script and forget to add -noprofile
parameter.
There is a simple workaround for this problem. Just add random suffix to class name and you are good to go. See this script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$id = get-random $code = @" using System; namespace HelloWorld { public class Program$id { public static void Main(){ Console.WriteLine("Hello world!"); } } } "@ Add-Type -TypeDefinition $code -Language CSharp iex "[HelloWorld.Program$id]::Main()" |
Observe that we obtain random number in line 1, append it to the class name in line 6, and finally we modify the line 16 to use correct class. Now you can modify C# code and execute it without restarting PowerShell or doing other tricks.
Referencing assemblies
At the beginning of each C# code we import namespaces with the using
keyword. In order to do that PowerShell needs to be able to find these namespaces, so it needs to have correct DLL-s loaded. In VS we can add library by adding reference to a project. We can do the same in PowerShell by adding referencedAssemblies
parameter to our script:
1 2 |
$assemblies = ("System.Core","System.Xml.Linq","System.Data","System.Xml", "System.Data.DataSetExtensions", "Microsoft.CSharp") Add-Type -ReferencedAssemblies $assemblies -TypeDefinition $code -Language CSharp |
You can see that we first define an array of assemblies to load and then we pass this array as an argument to Add-Type
commandlet. Now PowerShell will take care of loading libraries and necessary namespaces will be available to you so you will be able to import them with using
.
What about custom libraries? In order to load them PowerShell needs to be able to locate them in the system. By default PowerShell will look for them in GAC. If you have custom assemblies to load, you can add them manually before adding your C# code. See the example:
1 2 3 |
Add-Type -Path "CustomLibrary.dll" $assemblies = ("Custom.Namespace.From.Library") Add-Type -ReferencedAssemblies $assemblies -TypeDefinition $code -Language CSharp |
In the first line we manually load required dll using Add-Type
commandlet. We can specify path to the library so PowerShell will find it easily and load with no problems. After that we will be able to import namespaces from custom dll.
However, this method will work only for managed dlls. If you want to load native library, you need to load it directly into the PowerShell process using native functions:
1 2 3 4 5 6 |
$loadLibrary = @' [DllImport("kernel32", SetLastError=true, CharSet = CharSet.Ansi)] public static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)]string lpFileName); '@ $Kernel32 = Add-Type -MemberDefinition $loadLibrary -Name 'Kernel32' -Namespace 'Win32' -PassThru $Kernel32::LoadLibrary("NativeLibrary.dll") | out-null |
We first define header of LoadLibrary
WinAPI function using P/Invoke. Next, we load it (line 5), finally we load native library using LoadLibrary function. Now we should be able to use native dlls in C# code.
Compilation errors
Compiling code in the presented way has one important drawback: it does not show compilation errors. You probably will not have the IDE nearby, so every typo will be difficult to track without information from the compiler what exactly went wrong. However, if you compile your code using -Path
parameter you will get description of problems with your code.
Running WPF from a PowerShell
Nothing stops you from running not only hello worlds but also more fancy application. PowerShell is integrated with .NET so you can create objects of C# classes and run almost arbitrary code. For instance, script below (created by The Scripting Guys) shows window using WPF:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
Add-Type -AssemblyName PresentationFramework [xml]$xaml = @" < Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="408"> < Grid> < Button x:Name="button1" Width="75" Height="23" Canvas.Left="118" Canvas.Top="10" Content="Click Here" /> < /Grid> < /Window> "@ Clear-Host $reader=(New-Object System.Xml.XmlNodeReader $xaml) $target=[Windows.Markup.XamlReader]::Load($reader) $control=$target.FindName("button1") $eventMethod=$control.add_click $eventMethod.Invoke({$target.Title="Hello $((Get-Date).ToString('G'))"}) $target.ShowDialog() | out-null |
You might need to run this script from STA PowerShell. Just start you PowerShell with -STA
parameter and you are good to go. For more details see this blog post.
Summary
Running arbitrary C# code can be very useful. You can always try to rewrite your application using PowerShell language, but it might be much easier just to load and execute existing code.