How to retrieve value of pwd from powershell in c# code - c#

I was looking for the answer at least 3 hours, but without success.
Everywhere are just pieces of code and I have no idea how to connect them together.
What I want to achieve:
I am creating dotnet-tool in which I need to process current working directory (value which is printed using pwd command in PS).
Dotnet tool will be installed in default directory C:\Users\Samuel\.dotnet\.store\myTool... but command can be invoked in any directory in PS using dotnet tool run myTool.
For example:
In PS I am in: C:\Users\Samuel\AxisRepos> and I run dotnet tool run myTool
In this case I want to retrieve C:\Users\Samuel\AxisRepos in C# code to find out, in which directory command was invoked.
So simply put, I want to do something like this in my dotnet tool:
class Program
{
static void Main(string[] args)
{
var pwd = GetPwdFromPowerShell();
}
static string GetPwdFromPowerShell()
{
string pwd = "";
// Retrieve current working directory from PS in which dotnet tool was invoked
return pwd;
}
}

pwd is just an alias for Get-Location which shows the current directory...
From c# you can use the built-in .net class System.IO.Directory specifically the method GetCurrentDirectory
So just update your code to:
static string GetPwd()
{
return System.IO.Directory.GetCurrentDirectory();
}
And from powershell this will have the same results:
[System.IO.Directory]::GetCurrentDirectory()

Related

Execute multiple command lines with the different process using .NET

i want to use CMD.exe in my C# Program.
The Problem is iam using normally The CMD to open Two Programs to convert Fotos from .png to .svg (ImageMagick.exe & Potrace.exe).
That happend via CMD.exe with Tow Command-lines
the firsstepe: magick convert image.png image.pnm the secondstep: potrace image.pnm image.svg
How to call the CMD.exe to do this two commandslin in my C# Program ?
i try this commands Lines but he call just the CMD.exe and do not anything.
If you use the C# Process class as shown in the code below, with process.WaitForExit() called after each process is started, then you can run the two commands separately as shown. I think the correct calls are in fact magick convert image.png image.pnm and then potrace image.pnm -b svg. This works for me and is what the code is doing.
The biggest problem here was getting C# to find the files it needs. To get this to work I put potrace in a subfolder of the project and copied it to the output directory, so you don't need to install it. I couldn't get this to work for ImageMagick. It appears to need to be installed to work, so in the end I just installed it. The files to be converted are similarly in a Files subfolder of the project with 'Copy to Output Directory' set on their project properties.
As some of the commenters have suggested it may be better to try one of the libraries that can be called directly from C# rather than starting separate processes, which are clearly more inefficient. However, my experience is that often this sort of wrapper project is not well-maintained versus the main project, so if the solution below works it may be fine.
I can upload the full project to GitHub if that would be helpful.
using System.Diagnostics;
using System.Reflection;
#nullable disable
namespace ImageMagicPotrace
{
internal class Program
{
static void Main(string[] args)
{
string path = Path.GetDirectoryName(Assembly.GetAssembly(typeof(Program)).Location);
string filesPath = Path.Combine(path, "Files");
string fileName = "image";
string imageMagick = "magick.exe";
string arg = $"convert \"{Path.Combine(filesPath, fileName + ".png")}\" \"{Path.Combine(filesPath, fileName + ".pnm")}\"";
RunProcess(imageMagick, arg);
string potrace = Path.Combine(path, #"potrace\potrace.exe");
string arg2 = $"\"{Path.Combine(filesPath, fileName + ".pnm")}\" -b svg";
RunProcess(potrace, arg2);
Console.WriteLine("Done");
}
private static void RunProcess(string executable, string arg)
{
Console.WriteLine(executable);
Console.WriteLine(arg);
ProcessStartInfo start = new ProcessStartInfo(executable)
{
WindowStyle = ProcessWindowStyle.Hidden,
Arguments = arg,
UseShellExecute = false,
CreateNoWindow = true
};
Process process = Process.Start(start);
process.WaitForExit();
}
}
}

How do I get windows registry property values using a PowerShell script run through a .Net PowerShell object?

I am using the following PowerShell script to return information about apps installed on a PC:
Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall"
When I run this script in the Windows Powershell ISE, I see something like this for results:
Hive: HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall
Name Property
---- -------- Connection Manager SystemComponent : 1
DirectDrawEx
Docker Desktop DisplayIcon : C:\Program
Files\Docker\Docker\Docker Desktop Installer.exe
DisplayName : Docker Desktop
DisplayVersion : 2.1.0.4
Version : 39773
InstallLocation : C:\Program Files\Docker\Docker
NoModify : 1
NoRepair : 1
Publisher : Docker Inc.
ChannelName : stable
ChannelUrl : https://download.docker.com/win/stable/appcast.xml
UninstallString : "C:\Program Files\Docker\Docker\Docker Desktop
Installer.exe" uninstall
I am calling the script from a C# .Net application as follows:
using (var ps = PowerShell.Create())
{
ps.Runspace = p_runspace;
ps.AddScript("Get-ChildItem ""HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall""");
IEnumerable<PSObject> commandOutput = ps.Invoke();
}
How do I programmatically get access in C# to the properties returned when I run the script (Publisher, DisplayName, DisplayVersion) returned when I run the script in the ISE?
I examined the properties on the PSObject in my app, they do not match up with the ISE.
Thanks, JohnB
Objects returned from PowerShell when invoked using PowerShell.Invoke() don't don't actually have all of the same members at the top level of the object, a bit of PowerShell "magic" makes it work that way from within a PowerShell session.
If you want a property value you need to inspect the Properties property of the returned PSObject:
foreach(PSObject obj in commandOutput) {
Object val = obj.Properties['PropertyName'].Value
}
where PropertyName is the name of the property you want.

Powershell -> exe -> powershell Write-Host Console Logging Failing

TLDW: To support a specific process I need to go from a powershell instances/module to kick off a known safe executable for some c# magic, which then in turn needs to kick and execute a powershell script before exiting.
Powershell Entry point
{
Known Good Exe
{
Powershell Work To Do
}
}
Now ideally, this would all run from a single console instance so that all of the output is simple to look at. The exe -> powershell logging all works fine and as expected when using powershell.Streams.... The write-hosts in the powershell work all show up in the console and I get all the info I want.
powerShell.Streams.Information.DataAdded += LogMessage;
The problem comes when the outer powershell module is introduced. This one is needed because the parent process and execution environment this is running from is powershell. Once this whole stack is started from within a powershell instance, I get console logging from the outer powershell, and from the exe. BUT all of the write-hosts from the inner powershell modules disappear.
I've tried disabling the stream redirects, and a few other things, but this isn't resolving in the manner I would hope. I'm hoping someone knows if there is a way to get this to work as it solves so many problems if it just would.
PowerShell Outer:
$C#Exe = Join-Path $PSScriptRoot $C#ExePath
$C#Args = #()
Write-Host "Hello world" # will show up
& $C#Exe $C#Args
C# Exe Code:
public static void Main(string[] args)
{
Console.WriteLine("Hello world"); #Will show up
var powerShell = PowerShell.Create().AddScript("PowerShellInner.ps1");
powerShell.Streams.Information.DataAdded += LogMessage<InformationRecord>;
powerShell.Streams.Warning.DataAdded += LogMessage<WarningRecord>;
powerShell.Streams.Error.DataAdded += LogMessage<ErrorRecord>;
powerShell.Streams.Verbose.DataAdded += LogMessage<VerboseRecord>;
powerShell.Streams.Debug.DataAdded += LogMessage<DebugRecord>;
StringBuilder resultString = new StringBuilder();
foreach (dynamic item in powerShell.Invoke().ToList())
{
resultString.AppendLine(item.ToString());
}
Console.WriteLine(resultString.ToString());
}
private static void LogMessage<T>(object sender, DataAddedEventArgs e)
{
var data = (sender as PSDataCollection<T>)[e.Index];
Console.WriteLine($"[{typeof(T).Name}] {Convert.ToString(data)}");
}
PowerShell Inner:
Write-Host "Hello world" #Wont show up
UPDATE on 1/22/2020
I can't fully explain why you're experiencing what you're seeing, but the following code works.
Output from executing PowerShellOuter.ps1
Code
Notes:
Your c# program doesn't show any code manipulating input arguments,
so I didn't model any input args
You mis-used the AddScript method. It needs the text of the script, not the script name
The code below assumes that the c# exe and the two PS scripts are in the same folder
In PowerShellInner.ps1, use write-output. write-host does not output data to PowerShell Objectflow Engine but rather, as the name implies, writes directly to the host and sends nothing to the PowerShell engine to be forwarded to commands later in the pipeline. See Write-Output or Write-Host in PowerShell
PowerShellOuter.ps1
cls
$CSharpExe = Join-Path $PSScriptRoot "PowerShellExecutionSample.exe"
Write-Host "Hello from PowerShellOuter.ps1"
&$CSharpExe
pause
PowerShellInner.ps1
Write-Output "Hello from PowerShellInner.ps1"
Code for PowerShellExecutionSample.exe
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Management.Automation;
namespace PowerShellExecutionSample
{
class Program
{
static void Main(string[] args)
{
PowerShellExecutor t = new PowerShellExecutor();
t.ExecuteSynchronously();
}
}
/// <summary>
/// Provides PowerShell script execution examples
/// </summary>
class PowerShellExecutor
{
public void ExecuteSynchronously()
{
using (PowerShell PowerShellInstance = PowerShell.Create())
{
//You mis-used the AddScript method. It needs the text of the script, not the script name
string scriptText = File.ReadAllText(string.Format(#"{0}\PowerShellInner.ps1", Environment.CurrentDirectory));
PowerShellInstance.AddScript(scriptText);
Collection <PSObject> PSOutput = PowerShellInstance.Invoke();
// loop through each output object item
foreach (PSObject outputItem in PSOutput)
{
// if null object was dumped to the pipeline during the script then a null
// object may be present here. check for null to prevent potential NRE.
if (outputItem != null)
{
Console.WriteLine(outputItem.BaseObject.ToString() + "\n");
}
}
}
}
}
}
Original Answer on 1/21/2020
Updating your question to break out your code out helped - thanks.
I think you have two issues:
1) Modify your c# program to obtain the streams coming from PowerShell Inner and make your c# program re-emit the data from the PowerShell Inner output streams. Here is a Microsoft blog entry I used to crack the same nut: Executing PowerShell scripts from C#
2) Modify your PowerShell Outer to obtain the streams coming from the c# program. Here is a blog entry that seems to crack that nut: How to redirect output of console program to a file in PowerShell. The heart of this is to execute the following from your PowerShell Outer:
cmd /c XXX.exe ^>log.txt 2^>^&1
Note: The ^ are really backticks

How to read powershell manifest file (.psd1) using c#

I am trying to access manifest details for a custom PowerShell module that has the manifest file stored along with the module(psm1) file in my directory structure.
What is the best way to access the manifest details like Description, GUID etc?
A psd1 file is a valid PowerShell script, so it's best to let PowerShell parse the file.
The simplest way is to use the Test-ModuleManifest cmdlet. From C#, that would look something like:
using (var ps = PowerShell.Create())
{
ps.AddCommand("Test-ModuleManifest").AddParameter("Path", manifestPath);
var result = ps.Invoke();
PSModuleInfo moduleInfo = result[0].BaseObject as PSModuleInfo;
// now you can look at the properties like Guid or Description
}
Other approaches cannot handle the complexities of parsing PowerShell, e.g. it would be easy to incorrectly handle comments or here strings when trying to use a regex.
Add a reference to System.Management.Automation. Then, use the following code to get a Hashtable from the .psd1 file.
static void Main(string[] args)
{
PowerShell ps = PowerShell.Create();
string psd = "C:\\Users\\Trevor\\Documents\\WindowsPowerShell\\Modules\\ISESteroids\\ISESteroids.psd1";
ps.AddScript(String.Format("Invoke-Expression -Command (Get-Content -Path \"{0}\" -Raw)", psd));
var result = ps.Invoke();
Debug.WriteLine(((Hashtable)result[0].ImmediateBaseObject)["Description"]);
}

Custom CMD Commands

Is it possible to create custom Command Prompt parameters for an application written and compiled in Visual Studio C#. I want the users of my application to be able to do stuff in it just by command line.
For example, if the user types in CMD application_name.exe -parameter to do the code that was assigned to parameter.
Yes, this is what string[] args is for in Program.cs.
The easiest way to ensure you have everything you need is to create your application from the Console Application template in Visual Studio.
To do this:
Go to File -> New Project...
Select Console Application from the list of available templates
Name your Solution
Hit OK to finish creating the Project
This will ensure you have all the references needed and that the Program.cs class is setup properly.
Here is a psuedo example of what you can do in the Main method of your program in order to accept parameters at the command line:
static void Main(string[] args)
{
if (args.Length <= 0) //Checking the Length helps avoid NullReferenceException at args[0]
{
//Default behavior of your program without parameters
}
else
{
if (args[0] == "/?")
{
//Show Help Manual
}
if (args[0] == "/d")
{
//Do something else
}
//etc
}
}
Then at the command prompt the user can type:
yourProgamName.exe /?
yourProgramName.exe /d
As mentioned in an answer that was removed, there is a great library for handling complex Command Line options Here:
NDesk Options
That is what the "args" parameter is for in your main method.
If your users run "application_name.exe -parameter" then args[0] will be "-parameter". You can use this to branch your application to the correct code.
Check out this question to see examples and good libraries for handling lots of command line parameters/arguments.
and if you don't have access to you main() for some reason, you can get your command line from Environment.CommandLine.

Categories