Using CSharp (C#) code in Powershell scripts

With Powershell we now got a new very powerful scripting language and many products like SharePoint provide their own extensions in form of CmdLets for administration purposes.

Customer like scripting languages as it allows them to write custom code without a need to run a compiler or to copy new executables to their production machines which usually requires a more complex approval process than deploying a script file or even to execute the commands within a command shell.

But on the other hand writing Powershell scripts requires to learn a new scripting language and to to use different tools than many of us are used to. As a developer I like the power of C# and IntelliSense in Visual Studio. In addition in the last couple of years I wrote many different tools in C# – and I don’t want to reinvent the wheel by porting them to Powershell.

So it would be great if the existing C# code could be reused inside Powershell without a need to implement it as Cmdlet.

And indeed the Powershell Version 2 provides a way to achieve this using the Add-Type Cmdlet which allows to generate a new .NET assembly using the provided C# source code in memory which can then be used by powershell scripts executed in the same session.

For demonstration purposes let’s assume we have the following simple C# code which allows to retrieve and set the RemoteTimeout value used in Content Deployment of SharePoint:

using Microsoft.SharePoint.Publishing.Administration;
using System;

namespace StefanG.Tools
{
    public static class CDRemoteTimeout 
    {
        public static void Get()
        {
            ContentDeploymentConfiguration cdconfig = ContentDeploymentConfiguration.GetInstance();
            Console.WriteLine(“Remote Timeout: “+cdconfig.RemoteTimeout);
        } 

        public static void Set(int seconds)
        {
            ContentDeploymentConfiguration cdconfig = ContentDeploymentConfiguration.GetInstance(); 
            cdconfig.RemoteTimeout = seconds;
            cdconfig.Update();
        } 
    }
}

Beside the standard .NET framework references this tool has references to two SharePoint DLLs (Microsoft.SharePoint.dll and Microsoft.SharePoint.Publishing.dll) which provide access to the SharePoint object model. To ensure that Powershell can correctly generate the assembly we need to provide this reference information to the Add-Type Cmdlet using the -ReferencedAssemblies parameter.

To specify the language of the source code (CSharp, CSharpVersion3, Visual Basic and JScript can be used) you need to provide the -Language parameter. CSharp is default.

On my system I have a csharptemplate.ps1 file which I can quickly copy and adjust to run my C# code if required which looks like this:

$Assem = (
…add referenced assemblies here…
    )

$Source = @”
…add C# source code here…
“@

Add-Type -ReferencedAssemblies $Assem -TypeDefinition $Source -Language CSharp 

For the above listed C# example the final powershell script – would look like this:

$Assem = (
    “Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c” ,
    “Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c”
    )

$Source = @”
using Microsoft.SharePoint.Publishing.Administration;
using System;

namespace StefanG.Tools
{
    public static class CDRemoteTimeout 
    {
        public static void Get()
        {
            ContentDeploymentConfiguration cdconfig = ContentDeploymentConfiguration.GetInstance();
            Console.WriteLine(“Remote Timeout: “+cdconfig.RemoteTimeout);
        } 
         
        public static void Set(int seconds)
        {
            ContentDeploymentConfiguration cdconfig = ContentDeploymentConfiguration.GetInstance();
            cdconfig.RemoteTimeout = seconds;
            cdconfig.Update();
        }
    }
}
“@

Add-Type -ReferencedAssemblies $Assem -TypeDefinition $Source -Language CSharp 

[StefanG.Tools.CDRemoteTimeout]::Get()

[StefanG.Tools.CDRemoteTimeout]::Set(600)

The last lines in the above listed sample demonstrates how to call the C# methods from Powershell.

14 Comments


  1. Pingback from Enterprise App Deployment in SharePoint 2013 – central,fast and easy | SharePoint Corner

    Reply

  2. Thanks for this great post. Above code snippet is working example of how to write C# code in PowerShell Script.
    I have implemented exactly the same process as mentioned above in my project. I have a PowerShell script and then I added C# code in this PowerShell Script using above method.
    Thanks again for this post.

    Reply

  3. Great Article!
    I’m trying to use this method to use a web service in vb.net and execute the code using powershell. Can you please provide us with some information on how we can add web service references in the powershell script just like we added dll assemblies?
    Thanks

    Reply

    1. Hi Sahib,
      it would be required to create an assembly for the webservice proxy using the method outlined in the following article:
      https://msdn.microsoft.com/en-us/library/ms155134.aspx
      Then you can load this assembly into your powershell script similar to the Microsoft.SharePoint.dll in this article and you can call the web service methods from your code.
      Cheers,
      Stefan

      Reply

  4. Hi, I was wondering if you would be able to help me.
    I’m trying to make a PS script to run an executable in memory, from a base64 string. Powershell keeps giving the following error:
    Exception calling “Run” with “0” argument(s): “Parameter count mismatch.”
    At C:\Users\user\directory\script.ps1:21 char:1
    + [Runner.MainXT]::Run()
    + ~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : TargetParameterCountException
    My PS code is as follows:
    $Source = @”
    using System;
    using System.Reflection;
    namespace Runner
    {
    public static class MainXT
    { public static string b64 = “”;
    public static void Run()
    {
    byte[] bin = Convert.FromBase64String(b64);
    Assembly a = Assembly.Load(bin);
    MethodInfo method = a.EntryPoint;
    object o = a.CreateInstance(method.Name);
    method.Invoke(o, null);
    }
    }
    }
    “@
    Add-Type -TypeDefinition $Source -Language CSharp
    [Runner.MainXT]::Run()

    Reply

    1. Hi Matt,
      did you have a previous version of the csharp script with parameter in the same powershell window?
      You need to close the powershell window to ensure that the csharp code gets recompiled.
      Cheers,
      Stefan

      Reply

      1. Thanks for the quick reply 🙂
        I tried that, but it still gives the same error :\

        Reply

        1. Hi Matt,
          I tested your code and it works for me – of course it fails internally because b64 is an empty string and no valid assembly can be created from it.
          But I do not get a TargetParameterCountException.
          Cheers,
          Stefan

          Reply

          1. Thanks for that. Is it possible the error is caused by my choice of exes to convert to b64 and use?
            I created a winforms project and built it without adding anything, converted that to b64 and it runs perfectly. Other exes I’ve tried have been various things I’ve downloaded – installers etc. I guess their dependencies and such cause issues when running the program in this manner.


          2. Hi Matt,
            sorry – I cannot answer this. Never tried to instantiate a module in such a way.
            Cheers,
            Stefan


  5. Even though this page is almost a decade old, I give it a shot:

    Is there a possibility to pass variables from the Powershell part to the C# part? We are trying to store certain AD Groups into an array and then use them to “fill” a system which only allows c#.

    So we basically would need to pass this array from the ps to the c# part.

    Reply

    1. Hi René,
      yes you can do this. If you check the example you will see that a parameter is passed from PowerShell to C# in the Set method.
      Cheers,
      Stefan

      Reply

  6. C# Snippet to map volume to physical drive

    Source from: https://jrich523.wordpress.com/2015/02/27/powershell-getting-the-disk-drive-from-a-volume-or-mount-point/

    This is required for the below function (Get-Disk2Mount) to function

    Add-Type -TypeDefinition @”
    using System;
    using Microsoft.Win32.SafeHandles;
    using System.IO;
    using System.Runtime.InteropServices;

    public class GetDisk
    {
    private const uint IoctlVolumeGetVolumeDiskExtents = 0x560000;

    [StructLayout(LayoutKind.Sequential)]
    public struct DiskExtent
    {
    public int DiskNumber;
    public Int64 StartingOffset;
    public Int64 ExtentLength;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct DiskExtents
    {
    public int numberOfExtents;
    public DiskExtent first;
    }

    [DllImport(“Kernel32.dll”, SetLastError = true, CharSet = CharSet.Auto)]
    private static extern SafeFileHandle CreateFile(
    string lpFileName,
    [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
    [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
    IntPtr lpSecurityAttributes,
    [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
    [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes,
    IntPtr hTemplateFile);

    [DllImport(“Kernel32.dll”, SetLastError = false, CharSet = CharSet.Auto)]
    private static extern bool DeviceIoControl(
    SafeFileHandle hDevice,
    uint IoControlCode,
    [MarshalAs(UnmanagedType.AsAny)] [In] object InBuffer,
    uint nInBufferSize,
    ref DiskExtents OutBuffer,
    int nOutBufferSize,
    ref uint pBytesReturned,
    IntPtr Overlapped
    );

    public static string GetPhysicalDriveString(string path)
    {
    //clean path up
    path = path.TrimEnd(‘\’);
    if (!path.StartsWith(@”\.\”))
    path = @”\.\” + path;

    SafeFileHandle shwnd = CreateFile(path, FileAccess.Read, FileShare.Read | FileShare.Write, IntPtr.Zero, FileMode.Open, 0,
    IntPtr.Zero);
    if (shwnd.IsInvalid)
    {
    //Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());
    Exception e = Marshal.GetExceptionForHR(Marshal.GetLastWin32Error());
    }

    var bytesReturned = new uint();
    var de1 = new DiskExtents();
    bool result = DeviceIoControl(shwnd, IoctlVolumeGetVolumeDiskExtents, IntPtr.Zero, 0, ref de1,
    Marshal.SizeOf(de1), ref bytesReturned, IntPtr.Zero);
    shwnd.Close();
    if(result)
    return @”\.\PhysicalDrive” + de1.first.DiskNumber;
    return null;
    }
    }

    “@

    function Get-Disk2Mount
    {

    $MountInfos = Get-WmiObject Win32_Volume -Filter "DriveType='3'" | select Label,Name,DeviceID,Capacity,SystemName

    $MountPoints = @()

    foreach($info in $MountInfos)
    {
    $volumeID = (($info.DeviceID -split '\\') -match "Volume*").trim()

    $PhysicalDisk = ([getDisk]::GetPhysicalDriveString($volumeID) -split '\\')[-1]

    $mountPoint = New-Object -TypeName psobject

    $mountPoint | Add-Member -MemberType NoteProperty -Name 'Label' -Value $($info.label)
    $mountPoint | Add-Member -MemberType NoteProperty -Name 'Name' -Value $info.Name
    $mountPoint | Add-Member -MemberType NoteProperty -Name 'PhysicalDisk' -Value $PhysicalDisk
    $mountPoint | Add-Member -MemberType NoteProperty -Name 'Size(GB)' -Value $([math]::round($info.Capacity/1GB))
    $mountPoint | Add-Member -MemberType NoteProperty -Name 'HostName' -Value $info.SystemName

    $MountPoints += @($mountPoint)

    }

    return $MountPoints

    }

    Reply

Leave a Reply to Anonymous Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.