C# Newbie here. I am not sure if what I'm going for is even possible.
Basically, I'm trying to read the properties of a system object array within a Powershell object in C#.
For simplicity's sake, here is a basic Powershell code that has a similar output:
$object = New-Object -TypeName PSObject
$object | Add-Member -MemberType NoteProperty -Name "Name" -Value "Yad"
$object | Add-Member -MemberType NoteProperty -Name "Title" -Value "C# Noob"
$object | Add-Member -MemberType NotePropety -Name "Machine Services" -Value (Get-Service)
return $object
On the C# side of things, I am able to fetch the results by mapping the properties as below:
var shell = PowerShell.Create();
shell.Commands.AddScript("C:\\Scripts\\Powershell\\TestScript.ps1 -user " + userName);
Collection<PSObject> results = shell.Invoke();
Profile userProfile = new Profile(); //Profile class is declared prior this line
foreach (PSObject psObject in results)
{
userProfile.Name = Convert.ToString(psObject.Properties["Name"].Value);
userProfile.Title= Convert.ToString(psObject.Properties["Title"].Value);
userProfile.MachineServices= psObject.Properties["Machine Services"].Value;
}
With this, the 'userProfile' object is equivalent to the Powershell output.
Now, the 'MachineServices' property is an object that contains its own set of properties (status, name, and displayname in Powershell). Is it possible to call these properties and retrieve their values?
I tried something similar to below but, of course, I get an error as the Powershell script is completely separate and the properties are unknown to the C# code prior runtime.
userProfile.MachineServices.Status
Any ideas?
If you investigate Get-Service in PowerShell with .GetType() you can find out that it returns an Object array, where the members are of type ServiceController.
I haven't tried it in C# but you should be able to declare Profile.MachineServices as System.ServiceProcess.ServiceController[] and then use the assignment
userProfile.MachineServices= (System.ServiceProcess.ServiceController[])(psObject.Properties["Machine Services"].Value);
Once C# knows the correct object type, you can access the properties and methods, get IntelliSense and so on.
Related
I am trying to retrieve data from PowerShell into a c# object. The data I am looking for is returned from a PowerShell Invoke() of GetExecutingRequests on a remote web server. The issue I'm having is that I am not getting an error code, but the results from the Invoke() that I'm looking for are nowhere in the return data, or on the PowerShell object.
using (Runspace runspace = RunspaceFactory.CreateRunspace(cxn))
{
runspace.Open();
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = runspace;
string script = String.Format("Get-WmiObject
WorkerProcess -Namespace root\\WebAdministration -ComputerName {0} |
Invoke-WmiMethod -Name GetExecutingRequests", server);
ps.AddScript(script);
ps.AddParameter("OutputElement", new HttpRequest[0]);
var result = ps.Invoke();
}
}
This code executes, and returns a Collection with 29 items. However, none of them show the GetExecutingRequests results, and there is nothing relevant on the PowerShell object either.
I would like to get the output of GetExecutingRequests into a c# object, so I can do further processing. the PSDataStreams on the ps object also have no results.
Any help would be appreciated.
MORE INFO:
I was able to solve this with a change to the PowerShell script I was sending:
string script = String.Format("Get-WmiObject WorkerProcess -Namespace root\\WebAdministration -ComputerName {0} | Invoke-WmiMethod -Name GetExecutingRequests | %{{ $_ | Select-Object -ExpandProperty OutputElement }}", server);
I'm not quite sure if I can resolve the results directly, but I'd advise running below command to get some some more information on the object being returned. From there, you could look into how you'd handle that returning in C#.
Get-WmiObject -Class $(<scriptblock>) | get-member
Define the object types in C# & see if you're able to capture it in that way first.
If you're not able to make any progress with capturing the object type being returned in powershell yourself, it may be worth posting here to see if anyone else might be able to offer any insight/experience with workarounds for interfacing those objects types into C#.
If above really isn't possible, forcing the powershell to return everything from standard out would help find if it's writing anything meaningful that you can scrape/format in C#. I think the best way to do that in your quoted powershell command would be like so:
return $(Get-WmiObject
WorkerProcess -Namespace root\WebAdministration -ComputerName {0} |
Invoke-WmiMethod -Name GetExecutingRequests | *>&1)
This returns all 5+ standard outs from powershell to the return object (for those reading, see these docs on ps streams). Youshould definitely be able to capture the return in your results variable, but it can't hurt to make sure you're able to throw/capture errors from overflow.
Hope this helps to continue the digging!
I would like to execute this event using c#.
Get-WinEvent -Path 'D:\Events\myevents.evt' -Oldest | Select-Object
-Property * | ForEach-Object {$_ | ConvertTo-Json}
I have written upto
path = "D:\\Events\\myevents.evt";
var powerShell = PowerShell.Create();
powerShell.AddCommand("Get-WinEvent");
powerShell.AddParameter("Path");
powerShell.AddArgument(path);
powerShell.AddParameter("Oldest");
powerShell.AddCommand("Select-Object");
powerShell.AddParameter("Property");
powerShell.AddArgument("*");
I am stuck on writing for ForEach-Object {$_ | ConvertTo-Json}. Let me know how to proceed.
Appreciate help.
Keith's answer is totally valid if Path come from trusted source.
Otherwise, it can be vulnerable for script injection. (demo https://gist.github.com/vors/528faab6411db74869d4)
I suggest a compromised solution:
wrap you script in a function, which takes dynamic arguments and Invoke it with AddScript(). Now you have a function in your powershell runspace/session. You can call this function with AddCommand() + AddParameter(). Remember, that you need to call powershell.Commands.Clear() after first Invoke, otherwise commands will be piped.
Code can look like that:
const string script = #"function wrapper($path) {return (Get-WinEvent -Path $path -Oldest | Select-Object -Property * | ForEach-Object {$_ | ConvertTo-Json}) }";
ps.AddScript(script);
ps.Invoke();
ps.Commands.Clear();
ps.AddCommand("wrapper").AddParameter("path", path);
You could just use the AddScript method:
powershell.AddScript("Get-WinEvent D:\Events\myevents.evt -Oldest | ConvertTo-Json");
I think you could also simplify that script and pipe directly to ConvertTo-Json.
e.g: if I run notepad.exe c:\autoexec.bat,
How can I get c:\autoexec.bat in Get-Process notepad in PowerShell?
Or how can I get c:\autoexec.bat in Process.GetProcessesByName("notepad"); in C#?
In PowerShell you can get the command line of a process via WMI:
$process = "notepad.exe"
Get-WmiObject Win32_Process -Filter "name = '$process'" | Select-Object CommandLine
Note that you need admin privileges to be able to access that information about processes running in the context of another user. As a normal user it's only visible to you for processes running in your own context.
This answer is excellent, however for futureproofing and to do future you a favor, Unless you're using pretty old powershell (in which case I recommend an update!) Get-WMIObject has been superseded by Get-CimInstance Hey Scripting Guy reference
Try this
$process = "notepad.exe"
Get-CimInstance Win32_Process -Filter "name = '$process'" | select CommandLine
if you put the following code in your powershell $profile file you can permanently extend the "process" object class and use the "CommandLine" property
example:
get-process notepad.exe | select-object ProcessName, CommandLine
code:
$TypeData = #{
TypeName = 'System.Diagnostics.Process'
MemberType = 'ScriptProperty'
MemberName = 'CommandLine'
Value = {(Get-CimInstance Win32_Process -Filter "ProcessId = $($this.Id)").CommandLine}
}
Update-TypeData #TypeData
I'm using powershell 7.1 and this seems to be built in to the process object now as a scripted property:
> (Get-Process notepad)[0].CommandLine
"C:\WINDOWS\system32\notepad.exe"
Interestingly, you can view its implementation and see that it partially uses the answer from PsychoData:
($process | Get-Member -Name CommandLine).Definition
System.Object CommandLine {get=
if ($IsWindows) {
(Get-CimInstance Win32_Process -Filter "ProcessId = $($this.Id)").CommandLine
} elseif ($IsLinux) {
Get-Content -LiteralPath "/proc/$($this.Id)/cmdline"
}
;}
Running Get-Member on a process shows that it is an instance of System.Diagnostics.Process, but that it has several properties that are scripted.
The other properties are FileVersion, Path, Product, and ProductVersion.
I'm using this as a reference: http://msdn.microsoft.com/en-us/library/dd182449(v=VS.85).aspx
So the implementation is very similar. I'm executing the following PowerShell command to retrieve process information from the intended computer through the 1-liner command below:
$computer = "Remote.Computer.Here"; Get-Process -computer $computer | Sort-Object WorkingSet -desc | Select-Object -first 10 | Format-Table -property name, ID, #{Expression= {$_.WorkingSet/1mb};Label="MemoryLoad";} -auto
The command above executes perfectly in PS window. However, when called from C# app code, I get no returns. Especially so, when I access it through the following:
PowerShell shell = PowerShell.Create();
shell.AddScript(CmdletMap[PSVocab.OsProcLoad]);
Collection<PSObject> obj = shell.Invoke();
DataTable dt = new DataTable();
dt.Columns.Add("ProcessName");
dt.Columns.Add("ID");
dt.Columns.Add("MemoryLoad");
DataRow row;
foreach (PSObject resultObject in obj)
{
row = dt.NewRow();
row["ProcessName"] = resultObject.Members["name"].Value;
row["ID"] = resultObject.Members["id"].Value;
row["MemoryCol"] = resultObject.Members["MemoryLoad"].Value;
dt.Rows.Add(row);
}
Doing a quick-watch of resultObject.Members[].Value would simply return null.
Any help?
Thanks.
Inspect shell.Streams.Error to see what error is happening with your invocation of the script.
In PowerShell, the default for a failing operation is to return nothing. PowerShell has several well-known streams, and your error is either lying in the error stream ([PowerShell].Streams.Error) or it is a terminating error ([Powershell].InvocationStateInfo.Reason).
Hope this helps,
Use two slightly different commands: one for C# (and console) and another for console only.
For invoking from C# and console:
$computer = "."
Get-Process -computer $computer | Sort-Object WorkingSet -desc | Select-Object -first 10 |
Select-Object -property name, ID, #{Expression= {$_.WorkingSet/1mb};Label="MemoryLoad"}
For interactive host (i.e. console, ISE, etc.) with prettier look:
$computer = "."
Get-Process -computer $computer | Sort-Object WorkingSet -desc | Select-Object -first 10 |
Select-Object -property name, ID, #{Expression= {$_.WorkingSet/1mb};Label="MemoryLoad"} |
Format-Table -AutoSize
It is the Format-Table that makes problems in C#. Do not use it in C#. As for console, it should be the last command in the pipeline, it produces objects for printing, not for further use. Example: the first command shows two columns name and ID but the second command does not get any name properties:
Get-Process | Format-Table -property name, ID
Get-Process | Format-Table -property name, ID | Select-Object name
According to the Technet, your syntax is wrong. . .
http://technet.microsoft.com/en-us/library/dd347630.aspx
Syntax
Get-Process [[-Name] ] [-ComputerName ]
[-FileVersionInfo] [-Module] []
Get-Process -Id [-ComputerName ]
[-FileVersionInfo] [-Module] []
Get-Process -InputObject [-ComputerName ]
[-FileVersionInfo] [-Module] []
Specifically, you need to use -computername, and not computer. And I have no idea what "Remote.Computer.Here" is. . .. you can use localhost.
edit
Nm, my coworker is an idiot. I just had to swap Remote.Computer.here with . and it looks all fine and dandy. See if that works.
I used this article to write my first own Powershell Cmdlet and Snapin. And it works fine.
But I return a set of objects from my own data class, which has four properties and I want Powershell to display just one of these properties by default. So I used this part of the article to create this format file:
<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
<ViewDefinitions>
<View>
<Name>RemoteFile</Name>
<ViewSelectedBy>
<TypeName>MyFullNamespace.RemoteFileData</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader />
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Filename</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
</ViewDefinitions>
</Configuration>
and link it in the Snapin:
public override string[] Formats
{
get { return new string[] { "MyFormatFilename.ps1xml" }; }
}
But when I install the Snapin with installutil, use Add-PSSnapin and call my Cmdlet, all Properties of the objects are displayed.
What am I doing wrong?
Everything looks correct except that I'm not sure how it behaves with no column header label defined. Try adding this node instead of your empty one:
<TableColumnHeader>
<Label>FileName</Label>
</TableColumnHeader>
Also make sure the file MyFormatFilename.ps1xml is in the same dir with the snapin when it is being loaded via Add-PSSnapin. Also, probably a duh, but double check for typos in the type name specified in the <TypeName> element.
Update: I tried your XML as listed above and it works for me. I copied it into Notepad2 and saved it to C:\temp\test.ps1xml then executed:
1# $obj = new-object psobject
2# $obj.psobject.TypeNames.Insert(0, 'MyFullNamespace.RemoteFileData')
3# Add-Member -InputObject $obj -MemberType NoteProperty -Name Filename `
-Value 'some-remotefile.txt'
4# Add-Member -InputObject $obj -MemberType NoteProperty -Name Dummy `
-Value 'dummy prop'
5# $obj.psobject.TypeNames
MyFullNamespace.RemoteFileData
System.Management.Automation.PSCustomObject
System.Object
6# $obj
Filename Dummy
-------- -----
some-remotefile.txt dummy prop
7# Update-FormatData C:\temp\test.ps1xml
8# $obj
Filename
--------
some-remotefile.txt
I would double check the full typename instance.GetType().FullName and also double check the contents of the format file. Make sure it is in the same dir that you registered the snapin from.