Load ActiveDirectory PowerShell Module in Runspace without AD Drive Letter - c#

I'm trying to load the ActiveDirectory module inside a custom SnapIn that I'm working on. However, when I do so I get the annoying error
"Error initializing default drive: 'Unable to find a default server
with Active Directory Web Services running.'"
which takes a good 15 seconds or so to timeout. From within a normal PowerShell console I realize you can set a variable to disable the AD: drive mapping but, I cannot seem to get that working from within C# code.
InitialSessionState initial = InitialSessionState.CreateDefault();
initial.Variables.Add(new SessionStateVariableEntry("ADPS_LoadDefaultDrive",
0,
string.Empty));
initial.ImportPSModule(new string[] { "ActiveDirectory" });
using (Runspace runspace = RunspaceFactory.CreateRunspace(initial))
{
runspace.Open();
using (Pipeline p = runspace.CreatePipeline())
{
Command getGroup = new Command("Get-ADGroup");
getGroup.Parameters.Add("Filter", this.Group);
p.Commands.Add(getGroup);
var results = p.Invoke();
this.WriteObject(results, true);
}
}
I've included what I think should work but, the ADPS_LoadDefaultDrive setting seems to be ignored as each time I try to make a call into the ActiveDirectory module I get the same web services error (along with a painful timeout)

Try to set ADPS_LoadDefaultDrive as an Environment variable, not a regular session variable.

Related

Execute Powershell cmdlet that have user prompts within C# WPF application

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.

Import module in Powershell remote session using c#

I am facing a problem that cause me headaches (literally), I hope if you can help me with it :
Given that my Powershell is on an other server than my application, in c#, you can create a "Powershell remote session" by defining an WSManConnectionInfo and using this during the "runspace" creation.
Somthing like :
var runspace = RunspaceFactory.CreateRunspace(WSManConnectionInfo);
But the problem is :
When we are working with the remote session, we can only use some commands(Not all the commands are available). So, you can't use the "Import-Module" command directly in the remote session.
So I am asking you if you can help me to find a solution in c# (or just some hint) to use imported module in the remote session.
I know there's a lot of solution out there (Pure Powershell command), but I am just not good enough to convert these solutions in c#.
Hmm, I am able to use Import-Module in a remote session e.g.:
var connectionInfo = new WSManConnectionInfo(new Uri("http://foo.acme.com:5985"));
var runspace = RunspaceFactory.CreateRunspace(connectionInfo);
runspace.Open();
using (var powershell = PowerShell.Create())
{
powershell.Runspace = runspace;
powershell.AddScript("Import-Module PSCX");
var results = powershell.Invoke();
powershell.AddScript("Get-Uptime | Out-String");
results = powershell.Invoke();
foreach (var result in results)
{
Console.WriteLine(result);
}
runspace.Close();
}
That outputs:
Uptime LastBootUpTime
------ --------------
10.20:21:06.6432615 6/26/2014 6:29:00 PM
Is it possible that your module is bit-specific, perhaps a 32-bit module that only loads into an x86 PowerShell session? The default session will be 64-bit (assuming the remote OS is 64-bit). If it turns out that your module is 32-bit specific, connect to the Microsoft.Powershell32 endpoint.
var connectionInfo = new WSManConnectionInfo(new Uri("http://foo.acme.com:5985"), "http://schemas.microsoft.com/powershell/Microsoft.PowerShell32", PSCredential.Empty);

How to use ActiveDirectory module (RSAT tools) in c#

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;

How to pass arguments to PowerShell via C#

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.

Mail enabling an AD account too soon after creation

I'm using the System.DirectoryServices.AccountManagement library to create an AD user account, then soon after using a PowerShell runspace to run the Enable-Mailbox command.
When I run this, it is sometimes failing on the Mail-Enable with the error "Active Directory account must be logon-enabled for the user's mailbox."
If rerun the same code, but just try to Mail-Enable the account only, it works fine. And again, other times it's able to create the AD account and Mail-Enable.
This link suggests that AD is still configuring the account when Exchange tries to mail-enable it:
http://social.technet.microsoft.com/Forums/en-US/exchangesvrdevelopment/thread/d53d91fd-c479-40e4-9791-32cb5da24721?prof=required
Here is the runspace code:
var connectionInfo = new WSManConnectionInfo(new Uri(ConfigurationManager.AppSettings["PSExchangeURI"]), ConfigurationManager.AppSettings["PSExchangeShellURI"], new PSCredential(ConfigurationManager.AppSettings["Username"], ConfigurationManager.AppSettings["Password"].ToSecureString()));
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos;
var command = new Command("Enable-Mailbox");
command.Parameters.Add("Identity", userPrincipal.UserPrincipalName);
command.Parameters.Add("Alias", userPrincipal.SamAccountName);
command.Parameters.Add("DisplayName", userPrincipal.DisplayName);
command.Parameters.Add("Database", ConfigurationManager.AppSettings["ExchangeDatabase"]);
using (var runspace = RunspaceFactory.CreateRunspace(connectionInfo)) {
using (var pipeline = runspace.CreatePipeline()) {
runspace.Open();
pipeline.Commands.Add(command);
var results = pipeline.Invoke();
}
}
Is there something else I can do to avoid this error (besides introducing a thread sleep)?
What you are seeing is likely to be down to replication time lag and the exchange server talking to a different DC then the AD user creation code.
What you should do is to line up exchange and your AD creation code to talk to the same DC.
From the PrincipalContext object under S.DS.AM read the DC's FQDN from the ConnectedServer property. Then pass in that value to the -DomainController parameter to the enable-mailbox cmdlet.
So I solve the "hard coding" issue of the DC by declaring a $dchostname variable in my code at run time. It queries the domain to find a suitable DC and then all processes in my script use that domain. This way even if I replace all my DCs, I don't have to update my code.
#Domain Controller Information
$dcs = (Get-ADDomainController -Filter *)
$dc = $dcs | Where {$_.OperationMasterRoles -like "*RIDMaster*"}
$dchostname = $dc.HostName

Categories

Resources