0

I have one C# console application. I want to access a remote machine from it, then run some PowerShell script files (available in the remote machine) using the same session before exit the session.

I now able to connect remote machine by refer to this post. This is the code I've tried...

ConnectionOptions options = new ConnectionOptions();
options.Username = "user1";
options.Password = "password123";
options.Impersonation = System.Management.ImpersonationLevel.Impersonate;

ManagementScope scope = new ManagementScope("\\\\compName\\root\\cimv2", options);
scope.Connect();

//Query system for Operating System information
ObjectQuery query = new ObjectQuery("SELECT * FROM Win32_OperatingSystem");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);

ManagementObjectCollection queryCollection = searcher.Get();
foreach (ManagementObject m in queryCollection)
{
    // Display the remote computer information
    Console.WriteLine("Computer Name     : {0}", m["csname"]);
    Console.WriteLine("Windows Directory : {0}", m["WindowsDirectory"]);
}

But I got stuck to run other PowerShell script files. I've got no idea on how to moving on to next step to run the .ps1 files which are in the remote machine.

HNA
  • 107
  • 2
  • 14
  • Why do you need a C# console application? Why not just [Enter-PSSession](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/enter-pssession?view=powershell-6)? – vasily.sib Feb 25 '19 at 02:47
  • I need it to be used in c# as it is the requirement. – HNA Feb 25 '19 at 02:56

1 Answers1

1

That console app is just repro'ing what PowerShell Remoting is doing or what MS SysInternals psexec is doing.

Why recreate this?

I get it's a requirement, but it's an over complication that you not have to manage, long term.

This just sounds like PSRemoting is not enabled, hence the reason for this console app.

Anyway, you are going to still instantiate the PowerShell host to run PowerShell stuff. So, you are just hosting PowerShell in your C# app.

Running PowerShell from C# is a common thing and very well documented. A quick search would give you lots of hits and samples to leverage.

How to run PowerShell scripts from C#

Calling C# code in Powershell and vice versa

add the following 'using' statements to import the required types:

using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Runspaces;

The following code block shows the RunScript method that does all the hard work. It takes the script text, executes it, and returns the result as a string.

private string RunScript(string scriptText)
{
    // create Powershell runspace

    Runspace runspace = RunspaceFactory.CreateRunspace();

    // open it

    runspace.Open();

    // create a pipeline and feed it the script text

    Pipeline pipeline = runspace.CreatePipeline();
    pipeline.Commands.AddScript(scriptText);

    // add an extra command to transform the script
    // output objects into nicely formatted strings

    // remove this line to get the actual objects
    // that the script returns. For example, the script

    // "Get-Process" returns a collection
    // of System.Diagnostics.Process instances.

    pipeline.Commands.Add("Out-String");

    // execute the script

    Collection<psobject /> results = pipeline.Invoke();

    // close the runspace

    runspace.Close();

    // convert the script result into a single string

    StringBuilder stringBuilder = new StringBuilder();
    foreach (PSObject obj in results)
    {
        stringBuilder.AppendLine(obj.ToString());
    }

    return stringBuilder.ToString();
}

How to Let the Script Interact with your Program

Before executing the script using the pipeline.Invoke() call, it's possible to expose the objects of your program to the script by using the method runspace.SessionStateProxy.SetVariable("someName", someObject).

This will create a named variable that the script can access (getting/setting properties, and even calling methods).

As an example, suppose we would expose the main form of the sample to the script by adding the SetVariable() call like this:

...
// open it

runspace.Open();
runspace.SessionStateProxy.SetVariable("DemoForm", this);
....
Then, the following script would print the caption of the window:

$DemoForm.Text
The following script would show all the properties and methods of the window:

$DemoForm | Get-Member

Please note, however, that any calls a script makes to your objects will be from another thread context, as pipeline.Invoke() seems to start its own worker thread. This means that your exposed objects will have to be thread-safe.

This could even be seen as a duplicate of this Q&A

Run PowerShell-Script from C# Application

Update for the OP

As for ...

The c# console app is required to act like the center to process several .ps1 script files,

So, the C# app is the C&C, replacing a PSRemoting / PSExec session. Yet, you don't say where the script files are or how you plan on getting them to the remote systems.

but first it needs to access the remote machine first.

Which is what PSRemoting ...

$s = New-PSSession -ComputerName (Get-Content Servers.txt) -Credential Domain01\Admin01
Invoke-Command -Session $s -ScriptBlock {
# Run these PowerShell commands or scripts.
Get-Process PowerShell
}

Or as a background job

$s = New-PSSession -ComputerName (Get-Content Servers.txt) -Credential Domain01\Admin01 
Invoke-Command -Session $s -ScriptBlock {
# Run these PowerShell commands or scripts.
Get-Process PowerShell
} -AsJob

and PSExec does.

psexec \RemotePCName [-u username[-p password]] command [arguments]

As for ...

Only after all .ps1 files ran successfully, it should be logged out from the remote machine and return the any output to the console.

Again, exactly what PSRemoting and PSExec does.

That's why I'm thinking to connect remote using c# first, then followed by calling all the .ps1 files.

Your console app simply has to do the above. This is doable, as thing as noted by these Q&A's, well, there just using command, not calling scripts, but the same rules apply...

Execute powershell script on a remote computer using C#

Invoke remote powershell command from C#

Example:

InitialSessionState initial = InitialSessionState.CreateDefault();
Runspace runspace = RunspaceFactory.CreateRunspace(initial);
runspace.Open();
PowerShell ps = PowerShell.Create();
ps.Runspace = runspace;
ps.AddCommand("invoke-command");
ps.AddParameter("ComputerName", "mycomp.mylab.com");
ScriptBlock filter = ScriptBlock.Create("Get-childitem C:\\windows");
ps.AddParameter("ScriptBlock", filter);
foreach (PSObject obj in ps.Invoke())
{
   // Do Something
}

but again, it’s just repro’ing the above. So, Yeppers, it’s a choice. Use PSRemoting, use PSExec, build a different solution.

postanote
  • 15,138
  • 2
  • 14
  • 25
  • Thanks for your prompt respond. Now I understand that it is really an over complication. But, I'm stuck. Not sure how it should be done. The c# console app is required to act like the center to process several .ps1 script files, but first it needs to access the remote machine first. Only after all .ps1 files ran successfully, it should be logged out from the remote machine and return the any output to the console. That's why I'm thinking to connect remote using c# first, then followed by calling all the .ps1 files. maybe you have some other suggestions I could follow? – HNA Feb 25 '19 at 06:16
  • the .ps1 files are already in the remote machine. do u mean it is doable with using command but not directly calling/add script? – HNA Feb 25 '19 at 07:29
  • Yes, any commands in those scripts, can be used in your C# app as notes above. The deal here, is how complicated / lengthy are those scripts? Do you really want to copy and paste all that back in to your app? If they are there, I'd just call them, but adding them to your app removes that dependency – postanote Feb 25 '19 at 08:29
  • `but adding them to your app removes that dependency` sorry but do you mean by this? – HNA Feb 25 '19 at 08:43
  • As per the sample show, just place the commands / code that needs to run in the script block --- ScriptBlock filter = ScriptBlock.Create("Get-childitem C:\\windows"); – postanote Feb 25 '19 at 21:57
  • I've enable-psremoting on the remote machine. and then I simply copy the example code and add `ps.AddCommand("Out-String")`. for `//do something` part I just print it out `console.write(obj.tostring())` but it does not print anything. what I missed? – HNA Feb 26 '19 at 09:05