How to handle Powershell Format-List output in C# - c#

PowerShell psh = PowerShell.Create();
//psh.AddScript("Get-Service | Format-List -Property Name, Status");
psh.AddScript("Get-Service");
Collection<PSObject> result = psh.Invoke();
foreach (PSObject msg in result)
{
Console.WriteLine(msg.Properties["Name"].Value);
}
In above example, if I use "Get-Service" alone, I am able to get the name and status of the service on the system. But if I use the same with "Get-Service | Format-List -Property Name, Status" get an exception.

When you run a command in PowerShell, the results are typically returned as CLR objects. So, your Get-Service command returns an object of type ServiceController, which is why you can query the name and status.
When you pass the output to Format-List, that command converts the objects into a list of objects that are designed for display of information: if you examine the results of Format-List you will see that it's a mixed array containing mostly FormatEntryData objects. Knowing this, it's obvious that you can't find the Status property on the output of Format-List: you no longer have a service object!
You can see the difference by running these two snippets, which will display the types of the objects in your results:
Get-Service | % { $_.GetType().FullName }
Get-Service | Format-List | % { $_.GetType().FullName }

Related

How to get output of PowerShell's Get-WmiObject in C#

I need to get which network interface is connected to which network. I found that this information is accessible in MSFT_NetConnectionProfile. Unfortunatelly I cannot access it directly from C# (I get ManagementException: Provider load failure on computer where it should run) but when I access it from PowerShell, it works. Then my idea is to run PowerShell command from C# but I cannot get the result.
using System.Management.Automation;
string command = "Get-WmiObject -Namespace root/StandardCimv2 -Class MSFT_NetConnectionProfile | Select-Object -Property InterfaceAlias, Name";
PowerShell psinstance = PowerShell.Create();
psinstance.Commands.AddScript(command);
var results = psinstance.Invoke();
foreach (var psObject in results)
{
/* Get name and interfaceAlias */
}
The code runs without errors but results are empty. I tried even adding Out-File -FilePath <path-to-file> with relative and absolute file path but no file was created. I even tried old >> <path-to-file> but without luck. When I added Out-String then there was one result but it was empty string.
When I tested the commands directly in PowerShell then it worked. Is there a way how to get it in C#?
The PS commands must be constructed in a builder-pattern fashion.
Additionally, in PS Core the Get-WmiObject has been replaced by the Get-CimInstance CmdLet.
The following snippet is working on my env:
var result = PowerShell.Create()
.AddCommand("Get-CimInstance")
.AddParameter("Namespace", "root/StandardCimv2")
.AddParameter("Class", "MSFT_NetConnectionProfile")
.Invoke();

(Powershell & C#) ExchangeOnline Cmdlets partially working

The issue is a little awkward to explain but essentially I have a piece of C# code that connects & logs into ExchangeOnline via powershell (using System.Management.Automation.Powershell) from that I wish to run some more commands specific to the ExchangeOnline instance.
I've found that certain commands do not work such as "Get-Mailbox" or "Get-DistributionList". The error stream I have captured simply says the cmdlet does not exist. However if I try these commands in a powershell window it works.
The apparently problematic code I have running is pasted below (mix of both powershell and C#):
string FullTenantScript = #"
[string]$SizeOfAllMailboxes = ((get-exomailbox -ResultSize Unlimited | get-exomailboxstatistics).TotalItemSize.Value.ToMB() | measure-object -sum).sum
$TenantProperties = [pscustomobject]#{
'Primary Domain' = Get-AzureADDomain | Where-Object IsDefault -Match 'True' | Select -ExpandProperty Name
'Tenant ID' = Get-AzureADTenantDetail | select -ExpandProperty ObjectId
'Number of users' = #(Get-AzureADUser -all $true).count
'Number of Mailboxes' = #(get-exomailbox -ResultSize unlimited).Count
'Number of Devices' = #(Get-AzureADDevice -all $true).Count
'Number of Subscriptions' = #(Get-AzureADSubscribedSku).Count
'Number of Distribution Lists' = #(Get-DistributionGroup).Count
'Number of Dynamic DLs' = #(Get-DynamicDistributionGroup).Count
'Number of 365 Groups' = #(Get-UnifiedGroup).Count
'Size of all mailboxes' = $SizeOfAllMailboxes + ' MB'
}
$TenantProperties
";
PowerShell getTenantinfo = PowerShell.Create().AddScript(FullTenantScript);
Collection<PSObject> TenantInfoResult = getTenantinfo.Invoke();
I've read online that some of the older cmlets are being phased out (for example Get-Mailbox), so instead I use (get-exomailbox). I just don't get why I can run them in a powershell window but not in my program (as they should do essentially the same thing).
Please also note my program connects into Azure also (using Connect-AzureAD).
Any help or advice as to why this is happening would be greatly appreciated & any further info required I will provide.
Thanks,
Mouse

How to to Convert PSObject to Stream output or DataTable using C# Powershell System.Management.Automation

I need to convert PowerShell PSObject to stream in a sameway the way PowerShell does. PowerShell also provides the output on standard stream in generic way.
e.g. $PSVersionTable only outputs 2 column ( Name, Value ) and not the whole Hashtable.
So, I want each PSObject to process in a same standard way the way PowerShell does under the hood. Furthermore, I want to convert that to DataTable.
I know, there are other links illustrating this problem, but they are operating on all properties of PSObject. Somehow, PowerShell doing this stuff out of the box.
Do I need to convert PSObject to other type?
Examples
PowerShell script outputs $PSVersionTable as
PSVersion 5.1.17134.228
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
$host or Get-Host outputs as
Get-WebApplication outputs
Name Application pool Protocols Physical Path
---- ---------------- --------- -------------
IisHostedServer DefaultAppPool http C:\TestDirectory\Src\IisHostedServer
Get-ChildItem -Path "C:\TestDirectory\TestSubDir\Studio":
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 29.06.2018 11:54 TestFileDir
d----- 24.07.2018 16:37 TestSites
-a---- 13.08.2018 11:53 343 TestXml.xml
Similarly there could be other reference or custom types.
I want to get these outputs in a same way in C# and need to show them either as String or DataTable. Is there any conversion available from PSObject to stream or something which returns above output in completely generic way?
while I am dealing with following method in C#:
var outputCollection = new PSDataCollection<PSObject>();
outputCollection.DataAdded += OnOutputDataAdded;
powerShell.Invoke(null, outputCollection);
private void OnOutputDataAdded(object sender, DataAddedEventArgs e)
{
var records = (PSDataCollection<PSObject>) sender;
var data = records[e.Index];
var outputTable = ConverToLogableDataStream(data);
}
private void ConverToLogableDataStream(PSObject)
{
...
...
}
This question is quite old but have a look at ConvertTo-Json and work it into a data table or class of your choice from there (worked for me)
eg.
Powershell:
Get-Host | ConvertTo-Json
C#:
JsonConvert.DeserializeObject< List < classname > > (PSObject)

Get Memory (Private Working Set) of Process

My problem is what i am getting for WorkingSet is very different than Task manager Memory (Private Working Set). I have tried various solutions written on NET but the values are far too away from matching. Kindly help me get Memory (Private Working Set) from task manager.
script += string.Format(#"$Processes = Get-Process -ComputerName {0} | Sort-Object WorkingSet -desc | Select-Object;", remoteMachineName);
script += #"$ProcessArray= #();";
script += #"foreach ($process in $Processes) {";
script += #"$ProcessName = $process.ProcessName;";
script += #"$ProcessSize = $process.WorkingSet/1KB;";
script += #"$objAverage = New-Object System.Object;";
script += #"$objAverage | Add-Member -type NoteProperty -name Name -value $ProcessName;";
script += #"$objAverage | Add-Member -type NoteProperty -name Memory -value $ProcessSize;";
script += #"$ProcessArray +=$objAverage; }; ";
What is shown in Process Manager as Memory (Private Working Set) is the value of the performance counter \Process\working Set - Private.
You can retrieve this value with:
$ProcessPrivateSet = Get-Counter "\Process(instancename)\Working Set - Private"
$WSPrivateKiloBytes = $ProcessPrivateSet.CounterSamples[0].CookedValue / 1KB
$WSPrivateKiloBytes is now the same value as what you see in Process Manager.
The problem with retrieving this value for a distinct process is that performance counters name process instances by process name + instance count, rather than by process ID.
So, if you launch 1 instance of a Java application, you may retrieve the counter for the java.exe process, like so:
Get-Counter "\Process(java)\Working Set - Private"
Now, if you launch another one, you'll need to reference that one like this:
Get-Counter "\Process(java#1)\Working Set - Private"
and so on and on.
You can change this behavior by setting the ProcessNameFormat for performance counter objects on the local system like this:
$RegPath = "HKLM:\SYSTEM\CurrentControlSet\Services\PerfProc\Performance\"
Set-ItemProperty $RegPath -Name ProcessNameFormat -Value 2 -Type DWord
A value of 2 means "include process ID in instance names", a value of 1 (default) would mean "use an instance counter" (like shown above).
The new format will be processname_id
After changing the ProcessNameFormat, you can now retrieve performance counters for a specific Process ID, like so:
$javap = Get-Process -Name java | Select -First 1
Get-Counter "\Process(java_$($javap.Id))\"
Since you now have a distinct correlation between Get-Process output and the performance counters, you can now retrieve the "Private Working Set" value for each process, with a single Select-Object statement using a calculated property:
Get-Process java | Select Name,Id,#{Name="WSPrivate(KB)";Expression = {(Get-Counter "\Process($($_.Name)_$($_.Id))\Working Set - Private").CounterSamples[0].CookedValue / 1KB}}
It will take some time to retrieve each individual counter sample, so if you plan on doing this often or for a large set of processes, you might want to use a wildcard (*) and retrieve \Process(*)\Working Set - Private and then look at the InstanceName in each entry in CounterSamples

C# execute Powershell

So a while back I wrote a Powershell script that would Parse an IIS Log and then do some stuff (email, create a report, and other nifty stuff). Well I am working on excuting that from a C# console app. Before I post my code I just wanted to make one thing clear, i would like to try and stay away from log parser to parse this log cause of many reasons but one specifically, why use something when you can write something else to your liking ;). So here is my code
PS Script:
$t1 =(get-date).AddMinutes(-10)
$t2 =$t1.ToUniversalTime().ToString("HH:mm:ss")
$IISLogPath = "C:\inetpub\logs\LogFiles\W3SVC1\"+"u_ex"+(get-date).AddDays(-3).ToString("yyMMdd")+".log"
$IISLogFileRaw = [System.IO.File]::ReadAllLines($IISLogPath)
$headers = $IISLogFileRaw[3].split(" ")
$headers = $headers | where {$_ -ne "#Fields:"}
$IISLogFileCSV = Import-Csv -Delimiter " " -Header $headers -Path $IISLogPath
$IISLogFileCSV = $IISLogFileCSV | where {$_.date -notlike "#*"}
$tape = $IISLogFileCSV | Format-Table time,s-ip,cs-uri-stem | Out-Host
C# app thus far:
Runspace runSpace = RunspaceFactory.CreateRunspace();
runSpace.Open();
Pipeline pipeline = runSpace.CreatePipeline();
pipeline.Commands.AddScript(#"D:\PS-Scripts\IIS\IISLogScan.ps1");
Collection<PSObject> results = pipeline.Invoke();
foreach (PSObject obj in results)
{
Console.WriteLine(results);
}
Console.ReadLine();
Now when I run my App it just sits and doesnt display anything and i Set my breakpoints and it says that there is nothing to display within my foreach which i am completely hung up on cause running my ps1 script it works perfectly and shows many lines. Any insight anyone can give would be great.
Try changing in .ps1 file:
$global:tape =$IISLogFileCSV | Format-Table time,s-ip,cs-uri-stem
and in c#
pipeline.Commands.AddScript(#"D:\PS-Scripts\IIS\IISLogScan.ps1");
pipeline.Commands.AddScript("$tape");
pipeline.Commands.AddScript("out-string");
or in .ps1:
$tape =$IISLogFileCSV | Format-Table time,s-ip,cs-uri-stem
$tape
and in c#
pipeline.Commands.AddScript(#"D:\PS-Scripts\IIS\IISLogScan.ps1");
pipeline.Commands.AddScript("out-string");

Categories