When you run Get-ExecutionPolicy in PowerShell, it gets the effective execution policy. I need to know the best way to get that information in C#. I don't need to know how to change it like many other questions about PowerShell Execution Policy, I just need to know how to get it in C#.
Note:
PowerShell execution policies apply only on Windows.
With respect to Windows, the answer below covers both PowerShell editions.
It can be inferred from the docs that boxdog pointed to in a comment, but to spell it out:
using System;
using System.Management.Automation;
namespace demo
{
class ConsoleApp {
static void Main() {
using (var ps = PowerShell.Create()) {
var effectivePolicy = ps.AddCommand("Get-ExecutionPolicy").Invoke()[0].ToString();
ps.Commands.Clear();
Console.WriteLine("Effective execution policy: " + effectivePolicy);
}
}
}
}
Note:
The above assumes that you're using the PowerShell SDK - see this answer for the appropriate NuGet package to add to your project.
If you're using a PowerShell (Core) 7+ SDK, additional considerations apply:
On Unix-like platforms, execution policies fundamentally do not apply (Unrestricted is reported, though in effect it is Bypass), so the following applies to Windows only:
The LocalMachine scope of any - by definition install-on-demand - PowerShell (Core) 7+ version does not apply; only - if defined - the CurrentUser and GPO-based policies (which preempt the former) do.
On Windows:
In the absence of a relevant execution policy being defined, Restricted is the default, which categorically prevents execution of script files (.ps1).
If your application needs to execute .ps1 files when hosting the PowerShell SDK, for predictable behavior it is best to set the execution policy, for the current process only, as shown in this answer.
The most elegant solution would probably be to get the ExecutionPolicy registry key in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell. For this solution to work, your program needs to be running on the same architecture (x64 or x86) as the operating system it's running on or it won't be able to see the registry key. Code to do this would look something like this:
using Microsoft.Win32
...
string executionPolicy = Registry.GetValue(#"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell", "ExecutionPolicy", null)?.ToString();
If for any reason you can't do the first solution, the second way I would recommend is by using the System.Management.Automation.PowerShell NuGet package. This method would look something like this:
using(var ps = PowerShell.Create()){
ps.AddScript("Get-ExecutionPolicy");
Collection<PSObject> output = ps.Invoke();
Console.WriteLine($"Execution policy is {output[0]}")
}
If you really don't want to add an extra NuGet package to your project, there is another, but quite a bit messier way of doing this using System.Diagnostics.Process and it's output stream. It would look something like this:
var procInfo = new ProcessStartInfo("powershell.exe", "-Command \"Get-ExecutionPolicy\"")
{
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true
};
var proc = new Process
{
StartInfo = procInfo
};
proc.OutputDataReceived += Proc_OutputDataReceived;
proc.Start();
proc.BeginOutputReadLine();
Console.ReadLine();
...
private static void Proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(e.Data))
Console.WriteLine($"Execution policy is {e.Data}");
}
Related
I am attempting to create a WPF application that will execute some powershell commands using a 3rd party module (ShareGate). After extensive research and banging my head on the keyboard, I have gotten the application to at least execute the cmdlets I have asked for. The cmdlet in question, if run in powershell, will prompt the user to log into a web service using edge I believe. When running the cmdlet from the application, it throws an error which is misleading "during the last update edge was not able to be installed...."
I think that this error is coming up because this implementation isn't allowing powershell to pop open the browser like it does within a powershell window.
My question is this: "How can I redirect the user prompt to come up within the wpf application? or can I?"
here is my method:
public Task StartSGMigrations(IProgress<string> progress)
{
var sharegatePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\Apps\\ShareGate\\Sharegate.Automation.dll";
if (client != null)
{
try
{
InitialSessionState iss = InitialSessionState.CreateDefault();
iss.ImportPSModule(new string[] { sharegatePath });
using (Runspace myRunSpace = RunspaceFactory.CreateRunspace(iss))
{
myRunSpace.Open();
using (PowerShell powershell = PowerShell.Create())
{
Dictionary<string, string> parameters = new Dictionary<string, string>();
powershell.AddScript("New-CopySettings -OnContentItemExists IncrementalUpdate");
powershell.AddScript("Connect-box -email " + _admin + " -admin");
powershell.AddScript("Connect-Site -Url \"https://xxxx-admin.sharepoint.com\" -Browser");
powershell.Runspace = myRunSpace;
var results = powershell.Invoke();
var errors = myRunSpace.SessionStateProxy.PSVariable.GetValue("Error");
foreach (var result in results)
{
progress.Report(result.ToString() + Environment.NewLine);
}
}
myRunSpace.Close();
}
}
catch (Exception ex)
{
progress.Report(ex.Message);
}
}
else
{
progress.Report("not connected to Box");
}
return Task.CompletedTask;
}
}
This will be fairly tricky to do if the commands you are calling do not support noninteractive execution.
What's going on:
Your application using the PowerShell api to call your scripts. This part's good. The problem is your scripts are using functionality of the PowerShell host (possibly prompting for credentials). Because the runspace is not associated with a host, any interactive capabilities will simply fail.
So you'd need to attach a host in order for it to work as expected (and that host would need to work the same as PowerShell.exe/pwsh.exe for whatever purposes your underlying cmdlets need).
If there were lots of implementations of a PowerShell host in the wild, I'd link you to them. Unfortunately, there are not. So unless you want to go down a deep rabbit hole, I'd suggest these alternatives:
If the cmdlet supports providing credentials directly, try this
If it does not, see if it "persists" credentials for a given user. (That is, open up a shell, login, close the shell, open another shell, and see if you can use the module without providing credentials).
If credentials do persist (and you can't do option 1), you should be able to call PowerShell.exe/pwsh.exe once to log in, and then load code normally.
If the credentials do not persist, you're stuck in a much more unfortunate situation, leaving you with paths 3,4, or 5:
Call powershell.exe/pwsh.exe (hopefully in in a minimized window) and send the output back via JSON or CLIXML.
Go down the rabbit hole and build yourself a host.
Beg the cmdlet authors to better support noninteractive scenarios.
Between those options, I'd start with the last one.
Best of luck.
I am trying to install a windows service and start it afterwards.I do not understand why after a couple of cycles (install+uninstall) i cannot install or uninstall the service anymore since i get the error:
Another version of this product is already installed
but the service is no longer present in the Services window , nor is the installer present in the Programs section.
If i try to uninstall i get :
Error 1001: An exception occured while uninstalling.This exception
will be ignored and the uninstall will continue.However the uninstall
might not be fully uninstalled after the uininstall is complete.
I do not understand what i am doing wrong.
I have added my project output to all custom actions:
-Install
-Uninstall
-Commit
-Rollback
How is one supposed to perform clean installs/uninstalls ?
Installer Code
[RunInstaller(true)]
public partial class ProjectInstaller : Installer {
public ProjectInstaller() {
InitializeComponent();
this.AfterInstall += ProjectInstaller_AfterInstall;
this.BeforeUninstall += ProjectInstaller_BeforeUninstall;
}
private void ProjectInstaller_AfterInstall(object sender, InstallEventArgs e) {
StartService();
}
private void ProjectInstaller_BeforeUninstall(object sender, InstallEventArgs e) {
StopService();
}
private void StartService() {
Debugger.Launch();
bool isAdmin = IsAdmin();
if (isAdmin) {
using (ServiceController controller = new ServiceController(serviceInstaller1.ServiceName)) {
controller.Start();
}
} else {
ProcessStartInfo info = new ProcessStartInfo {
Verb = "runas",
FileName = "net",
Arguments = $"start {serviceInstaller1.ServiceName}"
};
Process.Start(info);
}
}
private void StopService() {
bool isAdmin = IsAdmin();
if (isAdmin) {
using (ServiceController controller = new ServiceController(serviceInstaller1.ServiceName)) {
controller.Stop();
}
} else {
ProcessStartInfo info = new ProcessStartInfo {
Verb = "runas",
FileName = "net",
Arguments = $"stop {serviceInstaller1.ServiceName}"
};
Process.Start(info);
}
}
private static bool IsAdmin() {
var identity = WindowsIdentity.GetCurrent();
var princ = new WindowsPrincipal(identity);
return princ.IsInRole(WindowsBuiltInRole.Administrator);
}
}
TEMP: Adding this answer, not sure it is relevant until we hear more comments from OP.
Custom Actions: MSI has built-in mechanisms to start / stop and install / uninstall services that are quite reliable. These involve populating a number of standard MSI tables. Using custom actions could trigger problems like you describe, that are hard to debug and solve.
Deployment Tools: What tool are you using?
Visual Studio Installer Projects have a number of severe limitations as explained here (one of which is lacking built-in support for service installation).
The free and Open Source WiX - here is a quick start tip answer.
Commercial tools (Advanced Installer, Installshield, PACE, etc...) are excellent for most things, especially bundling prerequisites and such things.
WiX Toolset: WiX is a free, Open Source alternative, and there are several other major deployment tools you can check. Advanced Installer has some free features, but I don't think that includes service installation. Worth a test though - nice features. Installshield has no free version that I know of, but is full-featured. PACE suite is the new kid on the block. I would test them all and pick one - just 2 cents.
WiX: Service Installation Samples:
Maybe see this hands on WiX-markup sample from Rainer Stropek: WiXSamples - github.com/rstropek. Please check the ServiceControl element.
How to create a Windows Service MSI Installer Using WiX, this is untested by me but looks OK: https://github.com/Robs79/How-to-create-a-Windows-Service-MSI-Installer-Using-WiX
MSI and WiX expert Chris Painter's IsWiX tutorials at: https://github.com/iswix-llc/iswix-tutorials. IsWiX is a front-end for WiX.
And finally expert Helge Klein has published a helpful and complete WiX real-world sample: https://helgeklein.com/blog/2014/09/real-world-example-wix-msi-application-installer/
In a PowerShell profile, one can identify the PowerShell host in order to do appropriate setup for that host's environment. For example:
if ($host.Name -eq 'ConsoleHost')
{
Import-Module PSReadline
# differentiate verbose from warnings!
$privData = (Get-Host).PrivateData
$privData.VerboseForegroundColor = "cyan"
}
elseif ($host.Name -like '*ISE Host')
{
Start-Steroids
Import-Module PsIseProjectExplorer
}
I would like to be able to do the equivalent identification from a C# context primarily because PowerShell ISE does not support Console.ReadLine so I want to know if it is safe to use it in the current PS host's environment.
I first explored trying to get the output of the Get-Host cmdlet from within C# (per Invoking a cmdlet within a cmdlet). After I located the Microsoft.PowerShell.Commands.Utility assembly (under C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0) I could compile this but it yielded null...
var cmd = new Microsoft.PowerShell.Commands.GetHostCommand();
var myHost = cmd.Invoke();
...while this would not compile due to the InternalHost class being (ironically!) internal:
var cmd = new Microsoft.PowerShell.Commands.GetHostCommand();
var myHost = cmd.Invoke<System.Management.Automation.Internal.Host.InternalHost>();
Next, I then modified my cmdlet to inherit from PSCmdlet rather than Cmdlet (to allow access to the SessionState), so I could then access the PS host object like this:
var psVarObject = SessionState.PSVariable.GetValue("Host");
Of course, that returns a pure Object, which I then needed to cast to... oh, wait... it's still internal!... so this would not compile:
string psHost = ((System.Management.Automation.Internal.Host.InternalHost)psVarObject).Name;
Leaving me no alternative but to use reflection on a foreign assembly (horrors!):
string psHost = (string)psVarObject.GetType().GetProperty("Name").GetValue(psVarObject, null);
That works, but is less than ideal, because reflecting upon any 3rd-party assembly is a fragile thing to do.
Any alternative ideas on either (a) identifying the host or, (b) backing up a bit, being able to use the host's own Read-Host cmdlet to get a typed input from a user?
You can just use Host property from PSCmdlet class. And if you want to do Read-Host:
Host.UI.ReadLine()
When getting
var psVarObject = SessionState.PSVariable.GetValue("Host");
You can cast it to System.Management.Automation.Host.PSHost instead of InternalHost
How can my C# code run git commands when it detects changes in tracked file? I am writing a VisualStudio/C# console project for this purpose.
I am new to the the .NET environment and currently working on integrating automated GIT commits to a folder. I need to automatically commit any change/add/delete on a known folder and push that to a git remote. Any guidance appreciated. Thank you.
Here is what I have and the last one is the one I need some guidance with:
Git repository initially set up on folder with proper ignore file (done).
I am using C# FileSystemWatcher to catch any changes on said folder (done).
Once my project detects a change it needs to commit and push those changes (pending).
Tentative commands the project needs to run:
git add -A
git commit "explanations_of_changes"
git push our_remote
NOTE: This code (with no user interaction) will be the only entity committing to this repo so I am not worried about conflicts and believe this flow will work.
I realize this is an old question but I wanted to add the solution I recently came across to help those in the future.
The PowerShell class provides an easy way to interact with git. This is part of the System.Management.Automation namespace in .NET. Note that System.Management.Automation.dll is available via NuGet.
string directory = ""; // directory of the git repository
using (PowerShell powershell = PowerShell.Create()) {
// this changes from the user folder that PowerShell starts up with to your git repository
powershell.AddScript($"cd {directory}");
powershell.AddScript(#"git init");
powershell.AddScript(#"git add *");
powershell.AddScript(#"git commit -m 'git commit from PowerShell in C#'");
powershell.AddScript(#"git push");
Collection<PSObject> results = powershell.Invoke();
}
In my opinion this is cleaner and nicer than using the Process.Start() approach. You can modify this to your specfic needs by editing the scripts that are added to the powershell object.
As commented by #ArtemIllarionov, powershell.Invoke() does not return errors but the Streams property has output information. Specifically powerShell.Streams.Error for errors.
If you want to do it in C#, you can call the external git command by Process.Start when you detect file change
string gitCommand = "git";
string gitAddArgument = #"add -A";
string gitCommitArgument = #"commit ""explanations_of_changes""";
string gitPushArgument = #"push our_remote";
Process.Start(gitCommand, gitAddArgument);
Process.Start(gitCommand, gitCommitArgument);
Process.Start(gitCommand, gitPushArgument);
Not the best solution but it works in C#
using System.Diagnostics;
using System.Text;
//Console.WriteLine(CommandOutput("git status"));
public static string CommandOutput(string command,
string workingDirectory = null)
{
try
{
ProcessStartInfo procStartInfo = new ProcessStartInfo("cmd", "/c " + command);
procStartInfo.RedirectStandardError = procStartInfo.RedirectStandardInput = procStartInfo.RedirectStandardOutput = true;
procStartInfo.UseShellExecute = false;
procStartInfo.CreateNoWindow = true;
if (null != workingDirectory)
{
procStartInfo.WorkingDirectory = workingDirectory;
}
Process proc = new Process();
proc.StartInfo = procStartInfo;
proc.Start();
StringBuilder sb = new StringBuilder();
proc.OutputDataReceived += delegate (object sender, DataReceivedEventArgs e)
{
sb.AppendLine(e.Data);
};
proc.ErrorDataReceived += delegate (object sender, DataReceivedEventArgs e)
{
sb.AppendLine(e.Data);
};
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
proc.WaitForExit();
return sb.ToString();
}
catch (Exception objException)
{
return $"Error in command: {command}, {objException.Message}";
}
}
Try LibGit2Sharp, a native implementation of git for .NET:
https://github.com/libgit2/libgit2sharp/
One alternative would be to setup Grunt and TaskRunner with your project.
Grunt should be able to provide the automation of detecting changes to a folder(or folders) in your project and execute the appropriate git commands to commit it.
Task Runner allows you to initialize and run Grunt from within Visual Studio.
The Visual Studio team has indicated that Task Runner is going to become integrated into future releases of Visual Studio, so this could be a long term solution.
Note: It has been mentioned in the comments, but I feel it worth mentioning again that auto-commiting anytime a file is saved to the repository isn't best practice. You want functional / atomic code changes to get pushed in, not simple text changes. Auto-Commit at your own risk.
The Package Manager Console is Powershell console. So you can run your git commands from there.
Is there a way to enable the ASP.NET Web Service Extension in IIS6 via C#? I'm trying to simplify a website setup program for people who haven't used IIS before.
C# NET. Framework usage:
Process.Start(#"C:\Windows\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis", "-i -enable");
CMD usage:
C:\Windows\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis -i -enable
It's useful.
Source: https://serverfault.com/questions/1649/why-does-iis-refuse-to-serve-asp-net-content
You could call out to WMI easily enough (System.Management namespace, IIRC) and I believe you can set it from there. However, it may well be much simpler to set it manually, you can't do it from within an ASP.NET site since your server won't be able to run it until it is set...
Principles of doing something similar may be found here
Looking around all the examples of this are written in vbscript. So I cheated and came up with this function:
static void EnableASPNET()
{
var file = "wmi.vbs";
using (var writer = new StreamWriter(file))
{
writer.WriteLine("Set webServiceObject = GetObject(\"IIS://localhost/W3SVC\")");
writer.WriteLine("webServiceObject.EnableWebServiceExtension \"ASP.NET v2.0.50727\"");
writer.WriteLine("webServiceObject.SetInfo");
}
var process = Process.Start("cscript", file);
process.WaitForExit();
File.Delete(file);
}
// if windows 2003
if (Environment.OSVersion.Version.Major == 5 &&
Environment.OSVersion.Version.Minor == 2)
{
DirectoryEntry folderRoot = new DirectoryEntry("IIS://localhost/W3SVC");
folderRoot.Invoke("EnableWebServiceExtension", "ASP.NET v2.0.50727");
}
Copied from: http://lastdon.blogspot.com/2006/12/setup-web-application-on-windows-2003.html
I believe you can also run the following command line:
C:\Windows\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis.exe -s W3SVC
And this will recursively enable the AND.NET framework v2.0.50727 for all configured websites.