I want to use specific command that are provided by the "Active Directory Administration with Windows PowerShell". So I installed this module on my server.
Now, I want to use these commands in my code. My project is in c# - ASP.NET.
Here's the code I use to call traditional "cmdlet" command (New-Mailbox, Set-User, ...) :
string runasUsername = #"Mario";
string runasPassword = "MarioKart";
SecureString ssRunasPassword = new SecureString();
foreach (char x in runasPassword)
ssRunasPassword.AppendChar(x);
PSCredential credentials =
new PSCredential(runasUsername, ssRunasPassword);
// Prepare the connection
var connInfo = new WSManConnectionInfo(new Uri("MarioServer"),
"http://schemas.microsoft.com/powershell/Microsoft.Exchange",credentials);
connInfo.AuthenticationMechanism =
AuthenticationMechanism.Basic;
connInfo.SkipCACheck = true;
connInfo.SkipCNCheck = true;
// Create the runspace where the command will be executed
var runspace = RunspaceFactory.CreateRunspace(connInfo);
//Params
....
// create the PowerShell command
var command = new Command("New-Mailbox");
command.Parameters.Add("Name", name);
....
// Add the command to the runspace's pipeline
runspace.Open();
var pipeline = runspace.CreatePipeline();
pipeline.Commands.Add(command);
// Execute the command
var results = pipeline.Invoke();
if (results.Count > 0)
System.Diagnostics.Debug.WriteLine("SUCCESS");
else
System.Diagnostics.Debug.WriteLine("FAIL");
runspace.Dispose();
This code work perfectly ! Great ! But let say I want to use "Set-ADUser", this command is from ActiveDirectory module (RSAT tools).
Given that all is set on the server (the module is installed), I tried to simply change "New-Mailbox" for "Set-ADUser" :
var command = new Command("Set-ADUser");
When I run the code, I have this error :
The term 'Set-ADUser' is not recognized as the name of a cmdlet, function, script file, or operable program.
So, that's my question :
How can we run command from the ActiveDirectory module (RSAT tools) in c# ? (Im using VS 2010).
As #JPBlanc pointed out in the comments section, you will need to ensure that the ActiveDirectory PowerShell module is loaded. PowerShell version 3.0 and later have module auto-loading enabled by default (it can be disabled), but if you're still targeting PowerShell 2.0, then you must first call:
Import-Module -Name ActiveDirectory;
.. before you can use the commands inside the module.
For the purposes of validation, you can use the Get-Module command, to ensure that the ActiveDirectory module has been imported.
Get-Module -Name ActiveDirectory;
If the above command returns $null, then the module is not imported. To verify that PowerShell can "see" the ActiveDirectory module, without actually importing it, run this command:
Get-Module -Name ActiveDirectory -ListAvailable;
Related
I'm working on a project that uses Exchange Online Powershell commands to retrieve the data from Microsoft. We are connecting to PS with CBA. Here is a code(I simplified it a little for demonstrating purposes):
public async Task ConnectToExhangeOnline() {
string ExolV2Cba = #"Param($Bytes, $Password, $AppId, $Organization)
Import-Module ExchangeOnlineManagement
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($Bytes, $Password)
Connect-ExchangeOnline -Certificate $cert -AppId $AppId -Organization $Organization";
using var powerShellProcessInstance = new PowerShellProcessInstance(new Version(5, 0), null, null, false);
powerShellProcessInstance.Process.StartInfo.FileName =
#"C:\Program Files\PowerShell\7\pwsh.exe";
using var runspace = RunspaceFactory.CreateOutOfProcessRunspace(new TypeTable(Enumerable.Empty<string>()), powerShellProcessInstance);
runspace.Open();
var parameters = new Dictionary<string, Object>();
parameters.Add("AppId", myClientId);
parameters.Add("Organization", myTenantDomain);
parameters.Add("Bytes", Convert.FromBase64String(myCertificate));
using PowerShell powershell = PowerShell.Create();
powershell.Runspace = runspace;
PSCommand command = new PSCommand().AddScript(ExolV2Cba);
foreach (var item in parameters)
{
command.AddParameter(item.Key, item.Value);
}
powershell.Commands = command;
var results = await Task.Factory.FromAsync(powershell.BeginInvoke(), powershell.EndInvoke);
var errors = powershell.Streams.Error.ReadAll();
}
Now, here is the problem. My organization parameter doesn't contain "onmicrosoft.com" part(and it's required due to documentation[see - https://learn.microsoft.com/en-us/powershell/exchange/app-only-auth-powershell-v2?view=exchange-ps#connection-examples%5C%5C\](https://i.stack.imgur.com/V7MKb.png))
So in production I got an error
For AppOnly flow Tenant "MyTenant.onmicrosoft.com" in token doesn't
match with Tenant "MyTenant" in request Url
However, when I run the same code locally on my pc everything works fine with no errors.
We are running all code in docker so the versions of Powershell and other libries are the same on prod and on local machine.
When I added the "onmicrosoft.com" suffix to the $Organization in production everything worked fine as well.
P.S.:
As you can see, in the script we're passing the $password parameter for building the certificate. However, we are not putting any value there so to be honest Idk how it's working. I suppose the certificate we are passing(as byte array) is enough(so Exchange Online doesn't require password). But when I try to run the same script from the Powershell Console on my PC (I fetched the certificate from debug) I'm constantly getting the prompt asking me for credentials(and I don't have ones since this is client's tenant).
I'm also wondering why in production it is failing on the moment when the actual script is executing(Get-ExoMailboxStatistics) and not on the moment when we are connecting (Connect-ExchangeOnline)
Got a mystery on my hands. This works locally on my IIS. But not on the on-prem server.
Downloaded ExchangeOnlineManagement v3.0.0 to my desktop and then copied the file over to C:\Program Files\WindowsPowerShell\Modules on the server. (Firewall said no to the PowerShell installation)
Installed on my local machine via PowerShell install command: Install-Module -Name ExchangeOnlineManagement
Online logs are showing this error: Cannot retrieve the dynamic parameters for the cmdlet. Unable to find type [Microsoft.Online.CSE.RestApiPowerShellModule.Instrumentation.LogLevel]
PowerShell ExchangeOnlineManagement v3.0.0 Source here https://www.powershellgallery.com/packages/ExchangeOnlineManagement/3.0.0/Content/netFramework%5CExchangeOnlineManagement.psm1 shows the type is indeed referenced in the dynamic dynamic parameters of several cmdlets.
Code:
var exchange = new ExchangeHelper();
using (var runspace = RunspaceFactory.CreateRunspace())
{
runspace.Open();
using (var psInstance = PowerShell.Create())
{
try
{
psInstance.Runspace = runspace;
exchange.AuthenticateInstance(psInstance, EnumExchangeUser.Web);
...
public PowerShell AuthenticateInstance(PowerShell psInstance, EnumExchangeUser enumExchangeUser)
{
var thumbprint = ConfigurationHelper.AuthThumbprint;
psInstance.Commands.Clear();
psInstance.AddCommand("Set-ExecutionPolicy")
.AddParameter("Scope", "Process")
.AddParameter("ExecutionPolicy", "Unrestricted")
.AddParameter("Confirm", false)
.AddParameter("Force", true)
.Invoke();
psInstance.Commands.Clear();
psInstance.AddCommand("Connect-ExchangeOnline")
.AddParameter("CertificateThumbPrint", thumbprint)
.AddParameter("AppID", "appID Guid goes here")
.AddParameter("Organization", "myorg.onmicrosoft.com")
.Invoke();
return psInstance;
}
Any idea why this is failing when calling "Connect-ExchangeOnline" and what I need to do?
Pulling out my hair here
I have a simple way to connect to a remote windows machine from a local windows machine using winrm.
Here is the powershell code that is working:
Set-Item WSMan:\localhost\Client\TrustedHosts -Value $ip -Force
$securePassword = ConvertTo-SecureString -AsPlainText -Force 'mypass'
$cred = New-Object System.Management.Automation.PSCredential 'Administrator', $securePassword
$cmd = {ls C:\temp}
Invoke-Command -ComputerName $ip -Credential $cred -ScriptBlock $cmd
I want to figure out how to do the exact thing in c#.
Also, it would be additionally helpful if someone tell me whether there is a method to send files in c# winrm.
Note: the is only a c# code needed on my local machine. The remote machine is already setup.
well, I figured out one way as I shall post below, but while it works fine on windows 8, it encounters the error "Strong name validation failed" on windows 7 so I should keep looking into this.
Still please feel free to post other ideas.
--> add System.Management.Automation.dll to your project.
WSManConnectionInfo connectionInfo = new WSManConnectionInfo();
connectionInfo.ComputerName = host;
SecureString securePwd = new SecureString();
pass.ToCharArray().ToList().ForEach(p => securePwd.AppendChar(p));
connectionInfo.Credential = new PSCredential(username, securePwd);
Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo);
runspace.Open();
Collection<PSObject> results = null;
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddScript(cmd);
results = ps.Invoke();
// Do something with result ...
}
runspace.Close();
foreach (var result in results)
{
txtOutput.AppendText(result.ToString() + "\r\n");
}
I've got an article that describes an easy way to run Powershell through WinRM from .NET at http://getthinktank.com/2015/06/22/naos-winrm-windows-remote-management-through-net/.
The code is in a single file if you want to just copy it and it's also a NuGet package that includes the reference to System.Management.Automation.
It auto manages trusted hosts, can run script blocks, and also send files (which isn't really supported but I created a work around). The returns are always the raw objects from Powershell.
// this is the entrypoint to interact with the system (interfaced for testing).
var machineManager = new MachineManager(
"10.0.0.1",
"Administrator",
MachineManager.ConvertStringToSecureString("xxx"),
true);
// will perform a user initiated reboot.
machineManager.Reboot();
// can run random script blocks WITH parameters.
var fileObjects = machineManager.RunScript(
"{ param($path) ls $path }",
new[] { #"C:\PathToList" });
// can transfer files to the remote server (over WinRM's protocol!).
var localFilePath = #"D:\Temp\BigFileLocal.nupkg";
var fileBytes = File.ReadAllBytes(localFilePath);
var remoteFilePath = #"D:\Temp\BigFileRemote.nupkg";
machineManager.SendFile(remoteFilePath, fileBytes);
Please mark as answer if this helps. I've been using this for a while with my automated deployments. Please leave comments if you find issues.
I having issues with passing arguments to PowerShell via C#
I am getting the following exception:
"A command that prompts the user failed because the host program or
the command type does not support user interaction. Try a host program
that supports user interaction, such as the Windows PowerShell Console
or Windows PowerShell ISE, and remove prompt-related commands from
command types that do not support user interaction, such as Windows
PowerShell workflows"
cs:
private static void RunPowershellScript(string scriptFile, string scriptParameters)
{
string scriptParameters = "param1 param2";
RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
runspace.Open();
RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
Pipeline pipeline = runspace.CreatePipeline();
Command scriptCommand = new Command(scriptFile);
Collection<CommandParameter> commandParameters = new Collection<CommandParameter>();
foreach (string scriptParameter in scriptParameters.Split(' '))
{
CommandParameter commandParm = new CommandParameter(null, scriptParameter);
commandParameters.Add(commandParm);
scriptCommand.Parameters.Add(commandParm);
}
pipeline.Commands.Add(scriptCommand);
Collection<PSObject> psObjects;
psObjects = pipeline.Invoke();
}
ps:
Function Foo ($arg1, $arg2)
{
Write-Host $arg1
Write-Host $arg2
}
Foo $args[0] $args[1]
What am i missing here? how can i make this work?
The exception is not about arguments. Either do not use commands that require host UI implemented (Write-Host included) or implement you own custom host (PSHost) and this UI (PSHostUserInterface). Here is the example of a simple host (and there is much more on MSDN about this, if you choose this way):
http://msdn.microsoft.com/en-us/library/windows/desktop/ee706559(v=vs.85).aspx
For simple tasks implementing a host with UI is too much, perhaps. You may consider simply to define a function Write-Host with the same arguments and implement it so that it works in your specific case (e.g. does [Console]::WriteLine(...)). This function should be defined in the script or better made available for it in a different way, e.g. invoke another script with it defined in the global scope.
P.S. And if you have your custom host then use one of the CreateRunspace() overloads that takes a host instance as an argument in order to link the host and the runspace.
I have a PowerShell script stored in a file, MergeDocuments.ps1. When I run the script from the Windows PowerShell command prompt it runs fine
.\MergeDocuments.ps1 1.docx 2.docx merge.docx
Calling the script from a Windows console application also runs fine.
When I tried calling the script from an Asp.Net web service, I faced some issues regarding registry access. I used impersonation and gave permission to Network Service account to the registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell to solve this problem
Next I faced issue about PowerShell being unable to create objects of type OpenXmlPowerTools.DocumentSource[], so I added the following to my script
Add-Type -Path "C:\Users\Administrator\Documents\WindowsPowerShell\Modules\OpenXmlPowerTools\OpenXmlPowerTools.dll"
Import-Module OpenXmlPowerTools
Now the current problem is that I am getting the error "The term 'Merge-OpenXmlDocument' is not recognized as the name of a cmdlet, ..."
How can I solve that?
PowerShell Script
Add-Type -Path "C:\Users\Administrator\Documents\WindowsPowerShell\Modules\OpenXmlPowerTools\OpenXmlPowerTools.dll"
Import-Module OpenXmlPowerTools
# The last argument is the path of the merged document
$noOfSourceDocs = ($($args.length) - 1)
# Create an array to hold all the source documents
[OpenXmlPowerTools.DocumentSource[]] $docs = New-Object OpenXmlPowerTools.DocumentSource[] $noOfSourceDocs
for ($i = 0; $i -lt $noOfSourceDocs; $i++)
{
$docs[$i] = New-Object -TypeName OpenXmlPowerTools.DocumentSource -ArgumentList $args[$i]
$docs[$i].KeepSection = 1
}
Merge-OpenXmlDocument -OutputPath $args[-1] -Sources $docs
Webservice .Net Code
using (new Impersonator(username, domain, password))
{
// create Powershell runspace
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
RunspaceInvoke invoker = new RunspaceInvoke(runspace);
invoker.Invoke("Set-ExecutionPolicy Unrestricted");
// create a pipeline and feed it the script file
Pipeline pipeline = runspace.CreatePipeline();
Command command = new Command(ConfigurationManager.AppSettings["PowerShellScript"]);
foreach (var file in filesToMerge)
{
command.Parameters.Add(null, file);
}
command.Parameters.Add(null, outputFilename);
pipeline.Commands.Add(command);
pipeline.Invoke();
runspace.Close();
}
Can you just try to install OpenXmlPowerTools module in the PowerShell System module path :
C:\Windows\system32\WindowsPowerShell\v1.0\Modules