powershell c# convert comobject to .net - c#

I'm using the following code to retrieve a specific address book from outlook:
$outlook = $(New-Object -ComObject Outlook.Application)
$Session = $outlook.Session
$Session.Logon()
$ab = $Session.AddressLists | ? {$_.Name -eq 'test'}
then attempting to use C# to convert the $ab object to .net using the following:
$source = #"
using Microsoft.Office.Interop.Outlook;
public unsafe static class OAB
{
public static void List(object Stream)
{
var i = Stream as Microsoft.Office.Interop.Outlook.AddressList;
return i;
}
}
"#
$cp = new-object System.CodeDom.Compiler.CompilerParameters
$cp.CompilerOptions = "/unsafe"
$cp.ReferencedAssemblies.Add('C:\wkdir\refassem\Microsoft.Office.Interop.Outlook.dll')
Add-Type -TypeDefinition $source -CompilerParameters $cp
The error I receive is:
Since 'OAB.List(object)' returns void, a return keyword must not be followed by an object expression
Disclaimer, I'm a complete newbie to C# so go easy on me!

You have your List function declared as a return type of void but you are returning a value. To fix, just change your function to this:
public static Microsoft.Office.Interop.Outlook.AddressList List(object Stream)
void functions mean that no value is going to be returns. If you do plan on returning a value, then you need to remove void and replace it with the type you want to return.

Related

Powershell command not recognized when calling from C#

This is in continuation to this Question here, I have a PowerShell command which I have created and am able to call the command in a PowerShell window, but when trying to call from C# method, I am getting error as the cmdlet is not recognized, I tried with other existing commands and get same error, so I suspect issue in Importing the Module, though I don't get that error in streams. Error. The only error I get is "Get-RowAndPartitionKey is not a recognized cmndlt, please check the spelling.....".
Would like to know if there is any other way, I should try it or if I can debug more here to see if my Module fetches all command or not. right now I am clueless how to fix this.
public string RunScript( string contentScript, Dictionary<string, EntityProperty> parameters )
{
List<string> parameterList = new List<string>();
foreach( var item in parameters )
{
parameterList.Add( item.Value.ToString() );
}
using( PowerShell ps = PowerShell.Create() )
{
IAsyncResult async =
ps.AddCommand( "Import-Module" ).AddArgument( #"C:\Users\...\.D.PowerShell.dll" )
.AddStatement()
.AddCommand( "Get-RowAndPartitionKey" ).AddParameter( "Properties", "test" )
.BeginInvoke();
StringBuilder stringBuilder = new StringBuilder();
foreach( PSObject result in ps.EndInvoke( async ) )
{
stringBuilder.AppendLine( result.ToString() );
}
return stringBuilder.ToString();
}
}
}
Below method do not return any error in Streams.Error or Verbose but no output also:
public async Task<IEnumerable<object>> RunScript( string scriptContents, List<string> scriptParameters )
{
// create a new hosted PowerShell instance using the default runspace.
// wrap in a using statement to ensure resources are cleaned up.
using( PowerShell ps = PowerShell.Create() )
{
// specify the script code to run.
ps.AddScript( scriptContents );
// specify the parameters to pass into the script.
ps.AddParameter( "Properties" ,"test") ;
// execute the script and await the result.
var pipelineObjects = await ps.InvokeAsync().ConfigureAwait( false );
return pipelineObjects;
}
}
scriptContent
"\"$path = 'C:\\Users...\\.TabularData.PowerShell.dll'\\r\\nImport-Module $path\\r\\nGet-RowAndPartitionKeys\""
The following is self-contained PowerShell sample code that uses on-demand compilation of C# code:
It shows that the approach works in principle, as described in this answer to your original question.
Prerequisites: The PowerShell SDK package and .NET runtime used in the C# project that calls your custom Get-RowAndPartitionKey" cmdlet must be compatible with the PowerShell SDK and .NET runtime that you used to compile the assembly DLL that houses that cmdlet, to be imported via Import-Module.
The sample code below ensures that implicitly, by running directly from PowerShell, using the Add-Type cmdlet to compile C# code on demand - it works in Windows PowerShell as well as in PowerShell (Core) 7+.
In practice I've found that a .NET Framework-compiled DLL (from Windows PowerShell) also works in PowerShell (Core) (.NET (Core) 5.0), but not vice versa.
It shows troubleshooting techniques, namely:
Adding the -Verbose switch to the Import-Module call to produce verbose output that lists the commands being imported from the given module (DLL).
Printing these verbose messages (look for // --- TROUBLESHOOTING CODE)
Printing any non-terminating PowerShell errors that occurred (as opposed to exceptions that you'd have to handle in C# code).
# Create a (temporary) assembly containing cmdlet "Get-RowAndPartitionKey".
# This assembly can directly be imported as a module from PowerShell.
# The cmdlet simply outputs "Hi from Get-RowAndPartitionKey" and
# echoes the elements of the list passed to -Properties, one by one.
$tempModuleDll = Join-Path ([IO.Path]::GetTempPath()) "TempModule_$PID.dll"
Remove-Item -ErrorAction Ignore $tempModuleDll
Add-Type #'
using System.Management.Automation;
using System.Collections.Generic;
[Cmdlet("Get", "RowAndPartitionKey")]
public class GetRowAndPartitionKeyCmdlet : PSCmdlet {
[Parameter] public List<string> Properties { get; set; }
protected override void ProcessRecord() {
WriteObject("Hi from Get-RowAndPartitionKey: ");
WriteObject(Properties, true);
}
}
'# -ErrorAction Stop -OutputAssembly $tempModuleDll
# Compile a C# class ad hoc to simulate your project, and call its static
# method, which imports the module and effectively calls
# Get-RowAndPartitionKey -Properties "foo", "bar"
(Add-Type #"
using System;
using System.Management.Automation;
using System.Collections.Generic;
using System.Text;
public static class Foo {
public static string RunScript(List<string> parameterList)
{
using (System.Management.Automation.PowerShell ps = PowerShell.Create())
{
IAsyncResult async =
// Add -Verbose to the Import-Module call, so that the list of
// commands being imported is written to the verbose output stream.
ps.AddCommand("Import-Module").AddArgument(#"$tempModuleDll").AddParameter("Verbose", true)
.AddStatement()
.AddCommand("Get-RowAndPartitionKey").AddParameter("Properties", parameterList)
.BeginInvoke();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject result in ps.EndInvoke(async))
{
stringBuilder.AppendLine(result.ToString());
}
// --- TROUBLESHOOTING CODE
// Print verbose output from the Import-Module call
foreach (var v in ps.Streams.Verbose) { Console.WriteLine("VERBOSE: " + v.ToString()); }
// Print any errors.
foreach (var e in ps.Streams.Error) { Console.WriteLine("ERROR: " + e.ToString()); }
// ---
return stringBuilder.ToString();
}
}
}
"# -ErrorAction Stop -PassThru)::RunScript(("foo", "bar"))
# Clean-up instructions:
if ($env:OS -eq 'Windows_NT') {
Write-Verbose -vb "NOTE: Re-running this code requires you to start a NEW SESSION."
Write-Verbose -vb "After exiting this session, you can delete the temporary module DLL(s) with:`n`n Remove-Item $($tempModuleDll -replace '_.+', '_*.dll')`n "
} else {
Write-Verbose -vb "NOTE: Re-running this code after modifying the embedded C# code requires you to start a NEW SESSION."
Remove-Item $tempModuleDll
}
On my Windows 10 machine, both from PowerShell (Core) 7.0.5 and Windows PowerShell 5.1, the above yields (clean-up instructions omitted) the following, showing that everything worked as intended:
VERBOSE: Loading module from path 'C:\Users\jdoe\AppData\Local\Temp\TempModule_11876.dll'.
VERBOSE: Importing cmdlet 'Get-RowAndPartitionKey'.
Hi from Get-RowAndPartitionKey:
foo
bar
Specifically, line VERBOSE: Importing cmdlet 'Get-RowAndPartitionKey'. indicates that the custom cmdlet was successfully imported into the session.

Run a PowerShell script from C#

I'm trying to build a graphic platform using Visual Studio. And I'm not a developer, I want to run PowerShell or batch files when I click a button. Thing is when I'm trying C# syntax it does not work even if I installed PowerShell extension.
I tried some code that I found on the internet, using process.start or trying to create a command in all cases the name of the command is not defined and it does not work.
private void Button1_Click(object sender, EventArgs e)
{
Process.Start("path\to\Powershell.exe",#"""ScriptwithArguments.ps1"" ""arg1"" ""arg2""");
}
I want to launch my .ps1 script but I get an error
name process is not defined
Calling C# code in Powershell and vice versa
C# in Powershell
$MyCode = #"
public class Calc
{
public int Add(int a,int b)
{
return a+b;
}
public int Mul(int a,int b)
{
return a*b;
}
public static float Divide(int a,int b)
{
return a/b;
}
}
"#
Add-Type -TypeDefinition $CalcInstance
$CalcInstance = New-Object -TypeName Calc
$CalcInstance.Add(20,30)
Powershell in C#
All the Powershell related functions are sitting in
System.Management.Automation namespace, ... reference that in your project
static void Main(string[] args)
{
var script = "Get-Process | select -Property #{N='Name';E={$_.Name}},#{N='CPU';E={$_.CPU}}";
var powerShell = PowerShell.Create().AddScript(script);
foreach (dynamic item in powerShell.Invoke().ToList())
{
//check if the CPU usage is greater than 10
if (item.CPU > 10)
{
Console.WriteLine("The process greater than 10 CPU counts is : " + item.Name);
}
}
Console.Read();
}
So, your query is also really a duplicate of many similar posts on stackoverflow.
Powershell Command in C#
Here's what it worked for me, including cases when the arguments contains spaces:
using (PowerShell PowerShellInst = PowerShell.Create())
{
PowerShell ps = PowerShell.Create();
string param1= "my param";
string param2= "another param";
string scriptPath = <path to script>;
ps.AddScript(File.ReadAllText(scriptPath));
ps.AddArgument(param1);
ps.AddArgument(param2);
ps.Invoke();
}
The .ps1 file would be something as this (make sure you declare the parameters in the .ps1 script):
Param($param1, $param2)
$message = $param1 + " & " + $param2"
[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
[System.Windows.Forms.MessageBox]::Show($message)
I find this approach very easy to understand and very clear.
string path = #"C:\1.ps1";
Process.Start(new ProcessStartInfo("Powershell.exe",path) { UseShellExecute = true })

PowerShell .NET object missing methods

I am working with the IO.Compression namespace in PowerShell and am running into an issue where Methods which are available in my C# code are not available in PowerShell
C# snippet
string archfile = #"c:\temp\a1.zip";
string source = #"C:\temp\testing\logs\BatchProcess\BatchProcess_2017_08_22.log";
using (ZipArchive archive = ZipFile.Open(archfile, ZipArchiveMode.Update))
{
archive.CreateEntryFromFile(source, #"myfolder\folder2\file.log");
}
Similar start of code in PowerShell
Add-Type -assembly System.IO.Compression.FileSystem
$archfile = "c:\temp\a1.zip"
# open mode: 1: Create, 2: Update
$archive = [System.IO.Compression.ZipFile]::Open("C:\temp\2.zip", 2)
$archive.CreateEntryFromFile($source, "myfolder\folder2\file.log")
A review of members shows that the function CreateEntryFromFile() (among others) is missing. The variable is a type of ZipArchive and a new file is created.
$archive | Get-Member displays:
TypeName: System.IO.Compression.ZipArchive
Name MemberType Definition
---- ---------- ----------
CreateEntry Method System.IO.Compression.ZipArchiveEntry CreateEntry(string entryName), System.IO.Compression.ZipArchiveE...
Dispose Method void Dispose(), void IDisposable.Dispose()
Equals Method bool Equals(System.Object obj)
GetEntry Method System.IO.Compression.ZipArchiveEntry GetEntry(string entryName)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Entries Property System.Collections.ObjectModel.ReadOnlyCollection[System.IO.Compression.ZipArchiveEntry] Entries {get;}
Mode Property System.IO.Compression.ZipArchiveMode Mode {get;}
Is this simply part of how PowerShell creates .NET objects where not all methods are (or can be) supported?
ZipArchive reference: https://msdn.microsoft.com/en-us/library/system.io.compression.ziparchive(v=vs.110).aspx
CreateEntryFromFile is an extension method, so it doesn't appear on the ZipArchive class in Powershell.
Option 1
In Powershell 3.0 or higher, you can declare the extension method for use in Powershell.
Add-Type -AssemblyName System.IO.Compression.FileSystem
Update-TypeData -TypeName System.IO.Compression.ZipArchive -MemberType
ScriptMethod -MemberName CreateEntryFromFile -Value {
switch ($args.Count)
{
2 { [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($this, $args[0], $args[1]) }
3 { [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($this, $args[0], $args[1], $args[2]) }
default { throw "No overload for CreateEntryFromFile takes the specified number of parameters." }
}
}
$archfile = "c:\temp\a1.zip"
# open mode: 1: Create, 2: Update
$archive = [System.IO.Compression.ZipFile]::Open("C:\temp\2.zip", 2)
$archive.CreateEntryFromFile($source, "myfolder\folder2\file.log")
Option 2
Just use the extension method as a plain old static method.
$archfile = "c:\temp\a1.zip"
# open mode: 1: Create, 2: Update
$archive = [System.IO.Compression.ZipFile]::Open("C:\temp\2.zip", 2)
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($archive, $source, "myfolder\folder2\file.log")
The second option is less code and also works in older versions of Powershell.
Reference: How do I use extension methods in ZipFileExtensionsClass?

PowerShell: Cannot spawn a new thread

I am attempting to spawn a new thread in PowerShell's command line using:
$t = New-Object System.Threading.Thread ([System.Threading.ThreadStart]{
Write-Host "Hello World"
});
$t.Start();
What happens is that a dialog appears saying "Powershell has stopped working".
I want to use my own Job class, written in C#, with start, pause, continue and stop methods. It uses a couple of WaitHandles to achieve this together with a new Thead instance.
I am aware of Start-Job etc, but would like to use real threads.
Any way?
EDIT: There seems to be a way https://davewyatt.wordpress.com/2014/04/06/thread-synchronization-in-powershell/
UPDATE I have packaged the below into a module called PSRunspacedDelegate, which you can install using Install-Package PSRunspacedDelegate. You can find documentation on GitHub.
Adam Driscoll's PowerShell Parallel Foreach explains that a thread running PowerShell code must have a Runspace.
In other words [Runspace]::DefaultRunspace cannot be null.
I ended up writing a RunspacedDelegateModule.psm1 module, with a function New-RunspacedDelegate that does the work.
Add-Type -Path "$PSScriptRoot\RunspacedDelegateFactory.cs"
Function New-RunspacedDelegate(
[Parameter(Mandatory=$true)][System.Delegate]$Delegate,
[Runspace]$Runspace=[Runspace]::DefaultRunspace) {
[PowerShell.RunspacedDelegateFactory]::NewRunspacedDelegate($Delegate, $Runspace)
}
RunspacedDelegateFactory.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Management.Automation.Runspaces;
namespace PowerShell
{
public class RunspacedDelegateFactory
{
public static Delegate NewRunspacedDelegate(Delegate _delegate, Runspace runspace)
{
Action setRunspace = () => Runspace.DefaultRunspace = runspace;
return ConcatActionToDelegate(setRunspace, _delegate);
}
private static Expression ExpressionInvoke(Delegate _delegate, params Expression[] arguments)
{
var invokeMethod = _delegate.GetType().GetMethod("Invoke");
return Expression.Call(Expression.Constant(_delegate), invokeMethod, arguments);
}
public static Delegate ConcatActionToDelegate(Action a, Delegate d)
{
var parameters =
d.GetType().GetMethod("Invoke").GetParameters()
.Select(p => Expression.Parameter(p.ParameterType, p.Name))
.ToArray();
Expression body = Expression.Block(ExpressionInvoke(a), ExpressionInvoke(d, parameters));
var lambda = Expression.Lambda(d.GetType(), body, parameters);
var compiled = lambda.Compile();
return compiled;
}
}
}
What I noticed is that it would still crash if I used Write-Host, but Out-File seems to be ok.
Here is how to use it:
Import-Module RunspacedDelegateModule;
$writeHello = New-RunspacedDelegate ([System.Threading.ThreadStart]{
"$([DateTime]::Now) hello world" | Out-File "C:\Temp\log.txt" -Append -Encoding utf8
});
$t = New-Object System.Threading.Thread $writeHello;
$t.Start();

Calling a function with named arguments in a hosted application

So I am hosting IronPython in my C# application. IronPhyton is used to implement a DSL for users. The DSL syntax should be something like this:
Ping(Message = "testOne1")
The hosting code looks like:
var engine = Python.CreateEngine();
var scope = engine.CreateScope();
Action<string> ping = (message) => Console.WriteLine(message.ToString());
scope.SetVariable("Ping", ping);
var script = #"
Ping(Message = ""testOne1"")
";
engine.Execute(script, scope);
But this does not work because Action<string> does not keep name of the argument. Calling it without the parameter name works as expected:
Ping("testOne1")
How do I store a function and call it with named arguments?
To use named arguments you'll have to define the method statically. For example, I'll just put all DSL operations into an Operations static class.
public static class Operations {
public static void Ping(string Message) {
Console.WriteLine(Message);
}
}
Then named arguments will work:
var engine = Python.CreateEngine();
var scope = engine.CreateScope();
// Load the assembly where the operations are defined.
engine.Runtime.LoadAssembly(Assembly.GetExecutingAssembly());
// Import the operations modules, settings their names as desired.
engine.Execute(#"
from Operations import Ping
", scope);
// Now named arguments will work...
var script = #"
Ping(Message = ""Ping!"")
";
engine.Execute(script, scope);
Now if I could give you some advise; I'd prefer to implement the actual Python API in Python, and have that call back into my .NET code as needed. For example, instead of having the "operations" defined in C#, you'd have an Operations.py file which defines your Python DSL:
# Get access to your .NET API
import clr
clr.AddReference("MyAPI")
import MyAPI
# Define the Ping call to call into your .NET API
def Ping(Message):
MyAPI.Ping(Message)
And your hosting code doesn't need to change at all.
Both are valid solutions, but the last one lets you iterate on your DSL easily.
Good luck!
The name of the parameter is defined by the name provided in the delegate type. In the case of Action<T>, the parameter name is obj.
public delegate void Action<in T>(
T obj
)
obj should work for you. Are you sure it isn't working? It works for me.
In an IronPython project I have a library:
namespace TestLibrary
{
public static class Test
{
public static readonly Action<string> WriteLine =
msg => Console.WriteLine(msg);
// the same works if I do this instead
//public static readonly Action<string> WriteLine = Console.WriteLine;
}
}
And this works:
from TestLibrary import Test
#Test.WriteLine(msg='foo') # error
Test.WriteLine(obj='foo') # works
Hosted, same deal:
var engine = Python.CreateEngine();
dynamic scope = engine.CreateScope();
Action<string> writeLine = msg => Console.WriteLine(msg);
// or even
//Action<string> writeLine = Console.WriteLine;
scope.writeLine = writeLine;
//engine.Execute("writeLine(msg='foo')", scope); // error
engine.Execute("writeLine(obj='foo')", scope); // works

Categories

Resources