I've been making a C# application that runs some Powershell stuff, the issue is that it doesn't seem to be be collecting the correct/all results of the Powershell commands.
The below code:
private void Button_Click(object sender, RoutedEventArgs e)
{
List<string> results = RunScript("Get-ADUser testuser2 -Properties *");
this.ShowMessageAsync("OK",results[0]);
}
private List<string> RunScript(string comand)
{
var powerShell = PowerShell.Create();
powerShell.Runspace = runspace;
powerShell.AddScript(comand);
Collection<PSObject> results = powerShell.Invoke();
List<string> resultsList = new List<string>();
if (powerShell.Streams.Error.Count > 0)
{
this.ShowMessageAsync("Something went wrong!", "Check error log for details!");
runspaceBox.AppendText(powerShell.Streams.Error[0] + "\r\n");
}
else
{
foreach (PSObject obj in results)
{
runspaceBox.AppendText(obj.ToString() + "\r\n");
resultsList.Add(obj.ToString());
}
}
return resultsList;
}
Results in:
{CN=testuser2,DC=testdomain,DC=com}
Putting the command into Powershell directly results in the below:
PS C:\Users\Ultimate-V1> Get-ADUser testuser2 -Properties *
[A bunch of extra stuff above]
Description :
DisplayName : Test User2
DistinguishedName : CN=Test
One,CN=Users,DC=testdomain,DC=com
Division :
DoesNotRequirePreAuth : False
[A lot of extra stuff below]
I've snipped a lot of the results out of the above command as it is very long.
What I can't work out is why it is only returning the Distinguished Name and not all of the results, this happens with some commands but not others. The command to return all AD groups on a user works fine for example.
If possible i'd love it to have EVERYTHING that the command returns, I will be formatting the resultsList later in the code.
Any help or pointers would be greatly appreciated!
Because you casted the result as a string, so you got a string representation of the complex object (whatever the result of the object's .ToString() method).
In PowerShell itself, it doesn't cast everything to a string to display it; it has various rules about how to display objects in general (usually a table or list format), and some types have specific formatters.
PetSerAl already answered in the comments, but the way to get a string representation of what PowerShell displays on the console is to send your output through Out-String:
powerShell.AddScript(comand).AddCommand("Out-String");
Related
I am trying to get informations about MFA in my C# application. I already achieved to get satisfying results in Powershell but i'm struggling to make the same thing in C#.
My code in Powershell :
Get-MsolUser -SearchString c.test#mytenant.com |
Where-Object {$_.StrongAuthenticationRequirements -like “*”} |
Select-Object UserPrincipalName, DisplayName, #{n='MFA';e=
{$_.StrongAuthenticationRequirements.State}}, #{n='Methods'; e=
{($_.StrongAuthenticationMethods).MethodType}}, #{n='Default Method'; e=
{($_.StrongAuthenticationMethods).IsDefault}}
UserPrincipalName : c.test#mytenant.com
DisplayName : Cyril test
MFA : Enforced
Methods : {OneWaySMS, TwoWayVoiceMobile, PhoneAppOTP,
PhoneAppNotification}
Default Method : {False, False, False, True}
As you can see, i get the MFA status and methods used.
Now, i want to do the same in C#.
My function :
public static List<string> GetMFA(Runspace runspace, string nom)
{
List<string> listResult = new List<string>();
try
{
Command getLicenseCommand = new Command("Get-MsolUser");
getLicenseCommand.Parameters.Add(new CommandParameter("SearchString", nom));
var pipe = runspace.CreatePipeline();
pipe.Commands.Add(getLicenseCommand);
var props = new string[] { "displayname", "userprincipalname", "StrongAuthenticationRequirements" };
Command CommandSelect = new Command("Select-Object");
CommandSelect.Parameters.Add("Property", props);
pipe.Commands.Add(CommandSelect);
// Execute command and generate results and errors (if any).
Collection<PSObject> results = pipe.Invoke();
if (results.Count != 0)
{
var error = pipe.Error.ReadToEnd();
if (error.Count > 0)
{
throw new Exception(error[0].ToString());
}
foreach (PSObject resultat in results)
{
string dn = resultat.Properties["displayname"].Value.ToString();
string upn = resultat.Properties["userprincipalname"].Value.ToString();
string mfa = resultat.Properties["StrongAuthenticationRequirements"].Value.ToString();
string res = dn + '/' + upn + '/' + mfa;
listResult.Add(res);
}
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
return listResult;
}
The "StrongAuthenticationRequirements" property isn't returning something like "Enforced" but
System.Collections.Generic.List`1[Microsoft.Online.Administration.StrongAuthenticationRequirement]
What am i missing here ?
I'm guessing the OP doesn't care any more, but for the next person who struggled with this like I have for the last few hours, I'm posting an answer.
In order to call these items, you must include the Microsoft.Online.Administration references which are included as DLLs within the MSOL PowerShell module package.
In Visual Studio:
Right Click References
Select "Add Reference"
Open the "Browse.."
Navigate to the installation location of the MSOL module. This may vary depending on if it was installed on the user/machine scope.
I added Microsoft.Online.Administration.PSModule
You will now be able to parse the results in c#.
Note: It was my experience that I needed to explicitly cast the items into a variable before I could use them.
Edit:
I removed the unnecessary reference to the resources.dll after further testing.
I am trying to write a web interface that will show the current VMs on a remote Hyper-V host.
So far, I have this
protected void getVMS(object sender, GridViewCommandEventArgs e)
{
//command to run
string cmdToRun = "get-vm -computername fsyovs02";
var shell = PowerShell.Create();
shell.Commands.AddScript(cmdToRun);
var results = shell.Invoke();
if (results.Count > 0)
{
var builder = new StringBuilder();
foreach (PSObject vm in results)
{
builder.Append(vm.BaseObject.ToString() + "\r\n");
}
ResultBox.Text = Server.HtmlEncode(builder.ToString());
}
}
This is returning something, but not what I want. For each VM, it is returning the line
Microsoft.HyperV.PowerShell.VirtualMachine
What I want is it to display exactly how it does via powershell.
Can anyone help me please - as I am going out of my mind!
Many Thanks
Mark
Sorry had to edit my answer as you cannot access those types from C#. You should then use the Members collection on PSObject to access particular properties of the VirtualMachine. Please check this approach in your case:
foreach (PSObject vm in results)
{
builder.Append(vm.Members["Name"].Value + "\r\n");
}
I just started playing with PowerShell two days ago. I am running a PowerShell script form a C# console application. I pass in a list of objects (MyObj1) as a parameter to the script and do some data manipulation to the object. However, when I call a function within the script I get a function not recognized error while printing out the errors. Here is a small sample of what I am doing.
public class MyObj1
{
public string Property1 {get; set;}
public int Property2 {get; set;}
}
Here is part of code that I am using to run the script:
var rs = RunspaceFactory.CreateRunspace();
rs.Open();
rs.SessionStateProxy.SetVariable("list", myObjectList);
rs.SessionStateProxy.SetVariable("xml", xml);
var ps = PowerShell.Create();
ps.Runspace = rs;
var psScript = System.IO.File.ReadAllText(#"C:\temp\ps.ps1");
ps.AddScript(psScript);
ps.Invoke();
foreach (var item in myObjectList)
{
Console.WriteLine(item.Property1 + "; " + item.Property1);
}
Console.WriteLine("\n================================ ERRORS ============================ \n");
foreach (ErrorRecord err in ps.Streams.Error)
{
Console.WriteLine(err.ToString());
}
And here is the actual script:
$list | ForEach {
$_.Property1 = $_.GetType()
DoSomeThing ([ref] $_)
}
Function DoSomeThing ($MyObject)
{
$Asset.Property2 = $Asset.Property2 + $Asset.Property2
}
Two days ago while I was playing with same script using some dummy classes like above, I was able to modify the data but since this morning, I have modified the script to use real classes. And Ctrl+Z will only take me back to a certain point in my editor. In the ForEach loop everything works until I cal the function.
As for the project, the plan is to push this application with required data manipulation in C# and then have some optional changes done in the script. The point is to avoid push to the application to production server and handling all/most data manipulation in the script.
Move function declaration to the beginnig of the script file.
Function DoSomeThing ($MyObject)
{
$Asset.Property2 = $Asset.Property2 + $Asset.Property2
}
$list | ForEach {
$_.Property1 = $_.GetType()
DoSomeThing ([ref] $_)
}
PS doesn't compile script, it executes it command by command. So you have to create/declare function before you use it.
I have recently began using C# to invoke powershell scripts, with some success :)
$spWeb = Get-SPWeb -Identity http://127.0.0.1
$listTemplate = [Microsoft.SharePoint.SPListTemplateType]::DocumentLibrary
$spWeb.Lists.Add($args, "", $listTemplate) > $myDirectory
$spWeb.Lists.Add returns a SharePoint GUID.
Question:
I simply wish to pass in the directory/filename in which the GUID will
be written. How could that be done?
I have posted on the MSDN forums here: http://goo.gl/5p0oz but have continued my search on stackoverflow due to a seemingly dead end thread. This post is a cumulative gathering of the information found through MSDN responses.
You can try using the Set-Content cmdlet instead, like this. You need to pass the $myFile as a string and Set-Content will do the rest for you -
Inside your script i.e. MyScript.ps1 here, you will have this piece of code -
param([string]$parameter, [string]$myFile)
try
{
$spWeb = Get-SPWeb -Identity http://127.0.0.1
$listTemplate = [Microsoft.SharePoint.SPListTemplateType]::DocumentLibrary
$guid = $spWeb.Lists.Add($parameter, "", $listTemplate)
$guid | Set-Content $myFile
}
catch
{
throw ("Failed. The error was: '{0}'." -f $_ )
}
How to Run:
Open Powershell Prompt and type this
.\MyScript.ps1 "someparam1" "D:\Output.txt"
C# Bit -
private PowerShell _ps;
_ps = PowerShell.Create();
var parameter = new List<string>
{
parameter,
myFile
};
var sr = new StreamReader(#"C:\MyScript.ps1");
_ps.AddScript(sr.ReadToEnd()).AddParameters(parameter);
_ps.Invoke();
I have 2 powershell scripts that I execute from c# which I'm using to first list message using an IMAP cmdlet and the 2nd script performs a view on a specific message. Both execute successfully from powershell, the 2nd one I am trying to retrieve an attachment and I see a bunch of data output to the console like so...
Sent from my iPhone
------=_NextPart_000_0027_01CCDAA7.399EBE00
Content-Type: image/jpeg; name="photo.JPG"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="photo.JPG"
/9j/4QH6RXhpZgAATU0AKgAAAAgACgEPAAIAAAAGAAAAhgEQAAIAAAAHAAAAjAESAAMAAAABAAYA
AAEaAAUAAAABAAAAlAEbAAUAAAABAAAAnAEoAAMAAAABAAIAAAEyAAIAAAAUAAAApAITAAMAAAAB
AAEAAIdpAAQAAAABAAAAuIglAAQAAAABAAABZgAAAABBcHBsZQBpUGhvbmUAAAAAAEgAAAABAAAA
SAAAAAEyMDA5OjA5OjIwIDE1OjEwOjU1AAAKgp0ABQAAAAEAAAE2kAAABwAAAAQwMjIxkAMAAgAA
ABQAAAE+kAQAAgAAABQAAAFSkQEABwAAAAQBAgMAoAAABwAAAAQwMTAwoAEAAwAAAAEAAQAAoAIA
BAAAAAEAAAZAoAMABAAAAAEAAASwpAYAAwAAAAEAAAAAAAAAAAAAAA4AAAAFMjAwOTowOToyMCAx
so it appears everything is fine so far - except there is I believe a difference in the function result as possible a stream/pipe from powershell?
The first one returns a collection of PSObjects which is called like so
PowerShell ps = PowerShell.Create();
ps.Runspace = runspace;
ps.AddScript(script);
ps.Invoke();
foreach (PSObject result in ps.Invoke())
{
dynamic val = result.BaseObject;
}
The 2nd script executes without error using Invoke() however trying to retrieve data via for each or
dynamic xx = ps2.Invoke();
xx is empty;
I thought maybe I need to use BeginInvoke and call asynchronously so I tried
static dynamic GotMail(dynamic o)
{
return o;
}
delegate dynamic SomeDelegate(dynamic o);
and attempted to use like so..
SomeDelegate sd = GotMail;
IAsyncResult ar = ps2.BeginInvoke();
dynamic val2 = sd.EndInvoke(ar);
and I recieve "The async result object is null or of an unexpected type." I'm not even passing parameters into the powershell scripts..everything is hardcoded - the script I run from powershell successfully is exactly what I execute from .NET
Any suggestions for this much appreciated.
Thanks
You're invoking the script twice by calling Invoke() twice. Remove the first call to invoke e.g.:
PowerShell ps = PowerShell.Create();
ps.Runspace = runspace;
ps.AddScript(script);
//ps.Invoke();
foreach (PSObject result in ps.Invoke()) {
dynamic val = result.BaseObject;
}