C# Pipeline.Invoke() not returning powershell collection - c#

On Windows 10, in C# I am trying to call a powershell script via Pipeline.Invoke() to retrieve all the disks on a network server, for each server on a network. But can't get it to return the data to C# when there are multiple disks on a server.
I have the following C# code:
foreach (var server in _collServers)
{
Pipeline pipeline2 = runspace.CreatePipeline();
pipeline2 = runspace.CreatePipeline();
scriptCommand = new Command(#"C:\DEV\Monitor\Monitor\bin\scripts\GetDisks.ps1");
CommandParameter commandParm = new CommandParameter("$server", server);
scriptCommand.Parameters.Add(commandParm);
pipeline2.Commands.Add(scriptCommand);
var collDisks = pipeline2.Invoke();
foreach (var item in collDisks)
{
var s = item.ToString();
}
}
which executes the following powershell script (GetDisks.ps1), with ComputerName hardcoded to a specific server for now:
$disks = Get-CimInstance -ClassName CIM_LogicalDisk -ComputerName SERVER01 | Where-Object { ($_.DeviceID -ge 'C') -and ($_.DriveType -eq 3)} | Select-Object DeviceID, VolumeName, Size, Freespace
$disksout = [System.Collections.Generic.List[PSObject]]::New()
ForEach($d in $disks)
{
$disk = [PSObject] #{
'DeviceID' = $d.DeviceID
'VolumeName' = $d.VolumeName
'Size' = $d.Size
'Freespace' = $d.Freespace
}
$disksout.Add($disk)
}
$disksout
but it doesnt return a PSObjects collection into collDisks.
It works in the ISE (and every re-arrangement I try seems to work there).
It works if I run it for my local machine by removing -ComputerName SERVER01
$disks = Get-CimInstance -ClassName CIM_LogicalDisk | Where-Object { ($_.DeviceID -ge 'C') -and ($_.DriveType -eq 3)} | Select-Object DeviceID, VolumeName, Size, Freespace
It doesnt make any difference if I try using PSCustomObject instead, or creating the object collection different ways.

Related

How to avoid sending a Connect/Disconnect-ExchangeOnline for every pwsh command in a c# Blazor app

I am developing a server side Blazor app to facilitate Exchange online management via a GUI. I have a helper class that setups the runspace and communicates the results back to the the Blazor component. I added the class via an scoped Dependency Injection.
The helper class is setup as:
InitialSessionState runspaceConfiguration = InitialSessionState.CreateDefault();
runspaceConfiguration.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.RemoteSigned;
runspaceConfiguration.ImportPSModule("ExchangeOnlineManagement");
runspaceConfiguration.ThrowOnRunspaceOpenError = true;
_runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
// Open local runspace
_runspace.Open();
_shell = PowerShell.Create();
_shell.Runspace = _runspace;
SetUpEventHandlers();
My function to execute commands is a follow:
public async Task ExecuteScript(string scriptContents)
{
_shell.AddScript(scriptContents);
OutputEventArgs rtc = new() {
Data = await _shell.InvokeAsync().ConfigureAwait(true)
};
_shell.Streams.ClearStreams();
_shell.Commands.Clear();
ResultsAvailable?.Invoke(this, rtc);
}
To connect to Exchnage Online I use a certificate, it looks like:
string? certificateThumbPrint = config.GetValue<string>("certificateThumbPrint");
string? appId = config.GetValue<string>("appId");
string? tenant = config.GetValue<string>("tenant");
logger.Info($"Running : Connect-ExchangeOnline -Certificate {certificateThumbPrint} -AppID {appId} -Organization {tenant} -ShowProgress $false -ShowBanner $false");
string scriptContents = $"Connect-ExchangeOnline -CertificateThumbPrint {certificateThumbPrint} -AppID {appId} -Organization {tenant}";
await objPowerShell.ExecuteScript(scriptContents);
I have setup EventHandler's that handle all the streams and results async, and everything works fine for one user. The problem is when I have multiple users. It still all works fine, except when one user disconnects the session, all active connections to the Exchange Online environment are disconnected. As I am using EXO v3, I cannot save the PSSession and import it.
So my question is how can I disconnect one user without having an impact on the other.
I did tried to do the same thing directly in PowerShell:
$Initialstate = [InitialSessionState]::CreateDefault()
$RunspacePool = [runspacefactory]::CreateRunspacePool(1,5)
$RunspacePool.Open()
$Runspace1 = [runspacefactory]::CreateRunspace()
$PowerShell1 = [powershell]::Create()
$PowerShell1.RunspacePool = $RunspacePool
$Runspace1.Open()
[void]$PowerShell1.AddScript({
$cert = Get-ChildItem Cert:\LocalMachine\My\0CF..4C17.
Connect-ExchangeOnline -Certificate $cert -AppID 70539...5889 -Organization myorg.onmicrosoft.com
})
$PowerShell1.Invoke()
$PowerShell1.Commands.Clear()
[void]$PowerShell1.AddScript({
Get-ConnectionInformation
})
$conn1 = $PowerShell1.Invoke()
$PowerShell1.Commands.Clear()
[void]$PowerShell1.AddScript({
Get-MailBox "ue*" -resultsize 2 | Select-Object PrimarySmtpAddress
})
$mbx1 = $PowerShell1.Invoke()
$mbx1 # Gets the info ok
$PowerShell2 = [powershell]::Create()
$PowerShell2.RunspacePool = $RunspacePool
[void]$PowerShell2.AddScript({
$cert = Get-ChildItem Cert:\LocalMachine\My\0CF.4C17D
Connect-ExchangeOnline -Certificate $cert -AppID 70539...5889 -Organization myorg.onmicrosoft.com
})
$PowerShell2.Invoke()
$PowerShell2.Commands.Clear()
[void]$PowerShell2.AddScript({
Get-ConnectionInformation
})
$conn2 = $PowerShell2.Invoke()
$PowerShell2.Commands.Clear()
[void]$PowerShell2.AddScript({
Get-MailBox "uz*" -resultsize 2 | Select-Object PrimarySmtpAddress
})
$mbx2 = $PowerShell2.Invoke()
$mbx2 # Gets the info ok
But when I execute now "Get-ConnectionInformation" I see 2 connections and "Disconnect-ExchangeOnline" disconnects them both. So, how can I only disconnect one ?

C# PowerShell application Exception

I am running Powershell commands within C# to get mailbox exchange folder delegates.
I am using the following 2 commands:
string scriptText = "Get-MailboxFolderPermission -Identity \"" + email + ":\\calendar\" | ? { ($_.user.tostring() -notlike \"Anonymous\") -and ($_.user.tostring() -notlike \"Default\") } | select User, AccessRights"
using (var powerShell = PowerShell.Create()) {
powerShell.AddScript(scriptText);
powerShell.Runspace = Runspace;
var iAsyncResult = powerShell.BeginInvoke();
psData = powerShell.EndInvoke(iAsyncResult);
}
string scriptText = "Get-MailboxPermission -Identity " + email + " | ? {($_.user.tostring() -ne “NT AUTHORITY\\SELF”) –and ($_.user.tostring() -ne “DOMAINDiscovery Management”) -and $_.IsInherited -eq $false -and ($_.AccessRights -match \"" + accessRights + "\")} | select User, AccessRights";
using (var powerShell = PowerShell.Create()) {
powerShell.AddScript(scriptText);
powerShell.Runspace = Runspace;
var iAsyncResult = powerShell.BeginInvoke();
psData = powerShell.EndInvoke(iAsyncResult);
}
At random intervals I get the following exception:
hostexception: a command that prompts the user failed because the host program or the command type does not support user interaction. the host was attempting to request confirmation with the following message: enter your credentials
I have no user prompts or anything in my commands. Does anyone know why my script is failing?
It turns out that it runs fine when the shell is run as admin.

Running a powershell script through a web api, want response of that api as a excel file which is generated by ps1 script

I have created a web api, through which I execute powerShell script Which create excel file with required data(audit log) into local System... But what I want is to get excel file as a response of that api. How can I achieve this?
My Powershell Script Code:-
# Create the session to Exchange Online
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -Uri https://outlook.office365.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection
# Import the Exchange Online commands
Import-PSSession $Session
$csvFile = "C:\Reports\auditlog2.csv"
# Setup our start and end dates to pull back events
#$start = Get-Date
$end = Get-Date
$start = $end.AddDays(-1)
$i = 1;
$startTime = Get-Date
do
{
$AuditData = Search-UnifiedAuditLog -StartDate $start -EndDate $end -RecordType SharePointFileOperation -ResultSize 5000 -SessionCommand ReturnLargeSet -SessionId "ExtractLogs" -SiteIds siteid
$ConvertedOutput = $AuditData | Select-Object -ExpandProperty AuditData | ConvertFrom-Json
$ConvertedOutput | SELECT CreationTime,UserId,Operation,Workload,ObjectID,SiteUrl,SourceFileName,ClientIP,UserAgent | Export-csv $csvFile -NoTypeInformation -Append -Force
Write-Host $i++ . $AuditData.Count
$i = $i + 1;
}
Until($AuditData.Count -eq 0)
Remove-PSSession $Session
My Web Api Code:-
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();
// Add your path or relative one
pipeline.Commands.AddScript(#"C:\Reports\spauditapi-Modified\spauditapi-master\SPAuditApi\Shell_Script\ReportGenerationforPermission.ps1");
//pipeline.Commands.AddScript(#"../Shell_Script/ReportGenerationforPermission.ps1");
pipeline.Commands.Add("Out-String");
//Collection <psobject/> results = pipeline.Invoke();
var results = pipeline.Invoke();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
return stringBuilder.ToString();

Cannot Pass Array from Powershell to C# Runspace Results

I am trying to get a list of database sizes to go from Powershell into a c# combobox, however I get no results. After furious googling I am still unsure of where to start. How can the $AllDB array be returned to c# as a result? Is this an Object type issue (which I tried many iterations of) or something concept I am missing? If I just run a basic cmdlet everything works and will populate the combobox (eg. Get-Process)
Also the code outputs the DB names and sizes to the console just fine, Out-String and Out-Default didn't work either.
Powershell code:
foreach ($db in Get-MailboxDatabase -identity exchangeserver\nameprefix*)
{
$mountedServer = $db.ServerName
$edbFilePath = $db.edbFilePath.PathName
$path = "`\`\" + $mountedServer + "`\" + $edbFilePath.Split(":")[0].ToString() + "$"+ $edbFilePath.Split(":")[1].ToString()
$dbsize = Get-ChildItem $path
$ReturnedObj = New-Object PSObject -Property #{
DatabaseIdentity = $db.Name
Size = [Math]::Round($dbsize.Length/1gb, 2)
}
$AllDB +=$ReturnedObj
}
$AllDB |Sort-Object Size|convertto-csv -NoTypeInformation | % { $_ -replace '"', ""}| % { $_ -replace ',', " "}
C# Code:
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddCommand(script);
var results = ps.Invoke();
foreach (PSObject result in results)
{
comboboxID.Items.Add(result);
}
}
runspace.Close();
}

Inserting a PowerShell command into c#

I want to use this PowerShell command in a C# project:
Get-VM -Name Win8-Henry | Get-VMNetworkAdapter | Select MacAddress
This is what I normally do in c#:
public static void MacAdd(string machineName,Runspace run) {
// Get-VM -Name Win8-Henry | Get-VMNetworkAdapter | Select MacAddress
Command command = new Command("Get-VM");
command.Parameters.Add("Name", machineName);
using (Pipeline hostPipeline = run.CreatePipeline())
{
hostPipeline.Commands.Add(command);
Collection<PSObject> echos = hostPipeline.Invoke();
hostPipeline.Stop();
}
}
What I need help with is adding the second command, and then using the pipeline.
Use the AddScript() method on the PowerShell class.
var Command = String.Format("Get-VM -Name {0} | Get-VMNetworkAdapter | Select MacAddress", computername);
var PowerShell = PowerShell.Create();
PowerShell.AddScript(Command);
PowerShell.Invoke();
Just add the other commands to the pipeline too :)
hostPipeline.Commands.Add(new Command("Get-VMNetworkAdapter"));
Just to share what I do when I have a script that I need to execute in an application. I can then replace the parameters (i.e., "$!$machineName$!$" easily and get the result cleanly.
Dim lxScript As XElement = <Script>
.{
Get-VM -Name $!$machineName$!$ | Get-VMNetworkAdapter | Select MacAddress
}
</Script>

Categories

Resources