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 System;
namespace StefanG.Tools
{
public static class CDRemoteTimeout
{
public static void Get()
{
ContentDeploymentConfiguration cdconfig = ContentDeploymentConfiguration.GetInstance();
Console.WriteLine(“Remote Timeout: “+cdconfig.RemoteTimeout);
}
{
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:
)
$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:
“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()
The last lines in the above listed sample demonstrates how to call the C# methods from Powershell.
Permalink
Pingback from Enterprise App Deployment in SharePoint 2013 – central,fast and easy | SharePoint Corner
Permalink
Thnks a lot!
I have used your tips to make a class to run one php script from task scheduler on windows, without timeout.
I have put here the code if can help any:
http://snipplr.com/view/91280/setup-one-timeout-value-for-downloadstring-method-of-webclient-class–in-powershell/
Permalink
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.
Permalink
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
Permalink
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
Permalink
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()
Permalink
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
Permalink
Thanks for the quick reply 🙂
I tried that, but it still gives the same error :\
Permalink
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
Permalink
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.
Permalink
Hi Matt,
sorry – I cannot answer this. Never tried to instantiate a module in such a way.
Cheers,
Stefan
Permalink
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.
Permalink
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
Permalink
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
}