Executing PowerShell Script with Parameters - c#

I'm adapting this solution to a Windows Forms solution. So far I've been able to execute the Get-WUList command with no problems. But it doesn't seem to go well with the Hide-WUUpdate. This is what I've tried so far:
public class PowerShellController : IPowerShell
{
//Created at a global scope so anyone can fetch it.
InitialSessionState initial;
RunspaceInvoke scriptInvoker;
Runspace runspace;
PowerShell ps;
//The View to Control
IView view;
//The Helper GridViewProcessor class
IGridViewProcessor gp;
//Initializing the Controller - Loads the Module.
public PowerShellController()
{
initial = InitialSessionState.CreateDefault();
initial.ImportPSModule(new string[] { #"C:\Users\Jose\Documents\WindowsPowerShell\Modules\PSWindowsUpdate\PSWindowsUpdate.psd1" });
scriptInvoker = new RunspaceInvoke();
scriptInvoker.Invoke("Set-ExecutionPolicy Unrestricted -Scope Process");
runspace = RunspaceFactory.CreateRunspace(initial);
runspace.Open();
using (ps = PowerShell.Create())
{
ps.Runspace = runspace;
}
//Console.WriteLine("Please Wait. This will take a while to load.");
}
public void SetView(IView view, IGridViewProcessor gp)
{
this.view = view;
this.gp = gp;
}
public void GetAvailableUpdates()
{
MessageBox.Show("Ok. The program will kind of hang. This is normal." +
"This Means that it will start looking for updates "
);
IEnumerable<PSObject> WUList; //Placeholder for the PS Executed Command
using (ps = PowerShell.Create())
{
//Adds the PowerShell Command
ps.Commands.AddCommand("Get-WUList");
//Executes the PowerShell command
WUList = ps.Invoke();
}
//Loads the Model - Can be later on rewritten for Ninject Support.
List<WindowsUpdate> model = new List<WindowsUpdate>();
int id = 1;
foreach (PSObject result in WUList)
{
WindowsUpdate item = new WindowsUpdate
{
Id = id,
Name = result.Members["Title"].Value.ToString(),
Size = result.Members["Size"].Value.ToString(),
Type = UpdateType.Undefined,
};
model.Add(item);
id++; //Icnrease ID count
//Console.WriteLine("Update Name {0} --- Size: {1}", result.Members["Title"].Value.ToString(), result.Members["Size"].Value.ToString());
}
//Adds it to the view:
view.AddUpdateToGrid(model);
}
public void HideSelectedUpdates(DataGridView grid)
{
//Gets SelectedUpdates to the WindowsUpdate model
var SelectedUpdates = gp.GetSelectedUpdates(grid);
using (ps = PowerShell.Create())
{
foreach (var update in SelectedUpdates)
{
ps.Commands.Clear();
ps.Commands.AddCommand("Hide-WUUpdate").AddParameter("Title",update.Name).AddParameter("Confirm", false);
//ps.Commands.AddCommand("Hide-WUUpdate -Title \""+update.Name+"\"");
var result = ps.Invoke();
}
}
MessageBox.Show("Updates Have been hidden");
}
}
The method I can't seem to work is the HideSelectedUpdates(DataGridView grid).
Script gets executed and no exceptions are thrown, but it doesn't seem to reflect any changes at all.
Any suggestions?

Related

Powershell script as a Topshelf/Windows service

I'd like to get notified when a specific pattern occurs in some text file so I wrote a simple PowerShell script:
Get-Content 'C:\\testfile.txt' -Wait -Tail 50 | Select-String -Pattern 'Sample pattern'
I'd rather run it as a topshelf service. I've already produced the following code:
public class MyService
{
private string doWork;
public string Path
{
get
{
return #"C:\\testfile.txt";
}
}
public void Start()
{
while (this.doWork)
{
var script = $"Get-Content '{Path}' -Wait -Tail 50 | Select-String -Pattern 'Sample pattern'";
using (PowerShell PowerShellInstance = PowerShell.Create())
{
PowerShellInstance.AddScript(script);
var PSOutput = PowerShellInstance.Invoke();
foreach (PSObject outputItem in PSOutput)
{
if (outputItem != null)
{
Console.WriteLine(outputItem.BaseObject + "\n");
}
}
if (PowerShellInstance.Streams.Error.Count > 0)
{
Console.Write.Write("Error");
}
}
}
}
public void Stop()
{
this.doWork = false;
}
}
public class Program
{
static void Main(string[] args)
{
var rc = HostFactory.Run(
x =>
{
x.Service<MyService>(
s =>
{
s.ConstructUsing(name => new MyService());
s.WhenStarted(tc => tc.Start());
s.WhenStopped(tc => tc.Stop());
});
x.RunAsLocalSystem();
x.StartAutomatically();
x.SetDescription("Sample Topshelf Host");
x.SetDisplayName("Sample display name");
x.SetServiceName("Sample service name");
});
var exitCode = (int)Convert.ChangeType(rc, rc.GetTypeCode());
Environment.ExitCode = exitCode;
}
}
The solutions builds fine.I ran it in a debug mode, but for some reason I don't see the expected output on the console so I assume that there is some problem which I tried to figure out myself but without effect.
Is there any way to make this program working as expected? If anyone could provide some guidance or even proper solution I'd be grateful.

How to retrieve results of Poweshell script back in C# objects?

I want to execute a PowerShell script through C#. My script will create a .csv file at a location specified. The below code creates a file at the location specified, but I want the code to return an object which has all the content/data the file has. Is that possible?
RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
using (Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration))
{
runspace.Open();
RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
Pipeline pipeline = runspace.CreatePipeline();
Command scriptCommand = new Command(#"C:\powershell.ps1");
Collection<CommandParameter> commandParameters = new Collection<CommandParameter>();
pipeline.Commands.Add(scriptCommand);
Collection<PSObject> psObjects;
psObjects = pipeline.Invoke();
}
Can you rather make use of Powershell APIs?
For example:
PowerShell psinstance = PowerShell.Create();
psinstance.AddScript(scriptPath);
var results = psinstance.Invoke();
More details can be found here:
https://blogs.msdn.microsoft.com/kebab/2014/04/28/executing-powershell-scripts-from-c/
You can return file content from powershell with this command and then write powershell output to the PSDataCollection.
private async Task<IEnumerable<string>> Process(string command)
{
var output = new List<string>();
using (var powerShell = System.Management.Automation.PowerShell.Create())
{
powerShell.AddScript(command);
var outputCollection = new PSDataCollection<PSObject>();
await Task.Factory.FromAsync(
powerShell.BeginInvoke<PSObject, PSObject>(null, outputCollection),
result =>
{
OnDeployEnd?.Invoke(this, EventArgs.Empty);
foreach (var data in outputCollection)
{
output.Add(data.ToString());
}
}
);
if (powerShell.HadErrors)
{
var errorsReport = powerShell.Streams.Error.GetErrorsReport();
throw new Exception(errorsReport);
}
}
return output;
}

How to use powershell invoke-command within C#?

If I run the below command inside of Powershell, it works as expected
invoke-command -computername [name] -scriptblock { ipconfig.exe > c:\ipconfig.txt }
But when I try to incorporate this into a c# function, I'm getting this error
{"Cannot bind parameter 'ScriptBlock'. Cannot convert the
\"ipconfig.exe > c:\ipconfig.txt\" value of type \"System.String\" to
type \"System.Management.Automation.ScriptBlock\"."}
even though I'm converting the scriptblock parameter value to a System.Management.Automation.ScriptBlock object? What am I doing incorrect?
private void btnInstallTest_Click(object sender, EventArgs e)
{
List<string> commands = new List<string>();
commands.Add("invoke-command");
List<Tuple<String, String>> parameters = new List<Tuple<string, string>>();
parameters.Add(new Tuple<string, string>("computername", #"server"));
parameters.Add(new Tuple<string, string>("ScriptBlock", #"ipconfig.exe > c:\ipconfig.txt"));
Collection<PSObject> psobject = runRemotePowerShellCommands(commands, parameters, "server", #"domain\user", convertToSecureString("password"));
}
private Collection<PSObject> runRemotePowerShellCommands(List<string> commands, List<Tuple<String, String>> parameters, string remoteMachineName, string domainAndUsername, SecureString securePW)
{
Collection<PSObject> psobjs = new Collection<PSObject>();
string result = "";
string shellUri = "http://schemas.microsoft.com/powershell/Microsoft.PowerShell";
PSCredential psCredential = new PSCredential(domainAndUsername, securePW);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(false, remoteMachineName, 5985, "/wsman", shellUri, psCredential);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos; //.Basic;
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
PowerShell powershell = PowerShell.Create();
for (int i = 0; i < commands.Count; i++)
{
if (commands[i].Contains(";"))
{
string[] commandsSplit = commands[i].Split(';');
}
else
{
powershell.AddCommand(commands[i]);
}
System.Management.Automation.ScriptBlock sb = null;
if (parameters != null)
{
foreach (Tuple<string, string> param in parameters)
{
if (param.Item1.ToLower() == "scriptblock")
{
sb = ScriptBlock.Create(param.Item2);
powershell.AddParameter(param.Item1, sb);
//powershell.AddParameter(param.Item1, param.Item2);
}
else
{
powershell.AddParameter(param.Item1, param.Item2);
}
}
}
if (runspace.RunspaceStateInfo.State == RunspaceState.Opened)
{
// do nothing, the runspace is already open
}
else
{
runspace.Open();
powershell.Runspace = runspace;
}
try
{
psobjs = powershell.Invoke();
if (powershell.HadErrors == true)
{
result = "Failed - " + powershell.Streams.Error[0].ToString();
result = result.Replace("\"", "*");
psobjs.Add(result);
}
}
catch (Exception ex)
{
result = "Failed: " + ex.Message;
psobjs.Add(result);
}
}
powershell.Commands.Clear();
}
return psobjs;
}
I feel like you've super over-complicated this:
using (PowerShell ps = PowerShell.Create())
{
ps.AddScript($#"Invoke-Command -ComputerName {name} -ScriptBlock {{ipconfig > C:\ipconfig.txt}}")
ps.Invoke()
}
Alternatively you can chain .AddCommand().AddParameter()

Run a list of PowerShell scripts on multiple machines all at once

I have what I believe to be a unique situation. I have about 15 PowerShell scripts that I want to run against a list of computers and have the scripts return back the output from each script on each host.
What I have works, however it does not appear run the scripts on each host simultaneously and is quite slow. Any help is appreciated.
for (int i = 0; i < hosts.Length; i++)
{
var remoteComputer = new Uri(String.Format("{0}://{1}:5985/wsman", "http", hosts[i]));
var connection = new WSManConnectionInfo(remoteComputer);
var runspace = RunspaceFactory.CreateRunspace(connection);
runspace.Open();
for (int ii = 0; ii < powerShellfiles.ToArray().Length; ii++)
{
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = runspace;
//ps.AddScript(powerShellfiles[ii]);
ps.AddScript(powerShellfiles[ii]);
IAsyncResult async = ps.BeginInvoke();
List<string> aa = ps.EndInvoke(async).SelectMany(x => x.Properties.Where(y => y.Name == "rec_num").Select(z => z.Value.ToString())).ToList();
keysFromhost.AddRange(aa);
}
};
};
Each item within powerShellfiles is the text from the .ps1 file itself.
All you need to do is use the Parallel.ForEach Async framework/Class and method.
This is a pretty simple solution Parallel will spawn individual threads for each item in the array that you provide and won't return until all threads has finished their execution you may also check the return value and see if all tasks completed successfully.
Now for your results you will need a thread safe collection, this one has been part of the .net framework since 3.0 I would use the one I specified below:
System.Collections.Generic.SynchronizedCollection<T>
Example:
private void RunPowerShell(string[] hosts)
{
Parallel.ForEach(hosts, (host) => {
var remoteComputer = new Uri(String.Format("{0}://{1}:5985/wsman", "http", hosts));
var connection = new WSManConnectionInfo(remoteComputer);
var runspace = RunspaceFactory.CreateRunspace(connection);
runspace.Open();
for (int ii = 0; ii < powerShellfiles.ToArray().Length; ii++)
{
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = runspace;
//ps.AddScript(powerShellfiles[ii]);
ps.AddScript(powerShellfiles[ii]);
IAsyncResult async = ps.BeginInvoke();
List<string> aa = ps.EndInvoke(async).SelectMany(x => x.Properties.Where(y => y.Name == "rec_num").Select(z => z.Value.ToString())).ToList();
keysFromhost.AddRange(aa);
}
};
});
}

How to get Exchange 2010 database size

I'm trying to get smallest Exchange database in my Exchange 2010 server using remote session.
I successfully connect to my exchange server and get database with properties. Some of them with value, but Properties DatabaseSize with Null value.
Did some body be able to get database size value?
Part of my code below:
static void Main(string[] args)
{
string exchangePowershellRPSURI = "http://my.domain/powershell?serializationLevel=Full";
PSCredential credentials = (PSCredential)null;
//Provides the connection information that is needed to connect to a remote runspace
// Prepare the connection
WSManConnectionInfo connInfo = new WSManConnectionInfo((new Uri(exchangePowershellRPSURI)),
"http://schemas.microsoft.com/powershell/Microsoft.Exchange", credentials);
connInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos;
connInfo.SkipCACheck = true;
connInfo.SkipCNCheck = true;
connInfo.SkipRevocationCheck = true;
// Create the runspace where the command will be executed
Runspace runspace = RunspaceFactory.CreateRunspace(connInfo);
// Add the command to the runspace's pipeline
runspace.Open();
//Represents the base functionality of a pipeline that can be used to invoke commands
Pipeline pipeline = runspace.CreatePipeline();
Command getMDB = new Command("Get-MailboxDatabase");
getMDB.Parameters.Add("Identity", "*");
getMDB.Parameters.Add("Status", null);
pipeline.Commands.Add(getMDB);
Collection<PSObject> select = pipeline.Invoke();
if (select.Count > 0)
{
foreach(PSObject obj in select)
{
var db = obj.Properties["DatabaseSize"].Value;
string name = obj.Properties["Name"].Value.ToString();
Console.WriteLine("Database Name: {0} Size: {1}", name, db);
}
}
else
{
Console.WriteLine("Failed to create email account");
}
runspace.Dispose();
Console.ReadLine();
}
I have found an solution
In the getMDB.Parameters.Add need for Status parameter to change value from "null" to "true"
getMDB.Parameters.Add("Status", null);

Categories

Resources