GuestProcessManager.StartProgramInGuest cannot run powershell block in guest os - c#

I want to run a powershell block that save last patched date to a user environment variable, but I tried both cmd.exe and powersehll.exe, only saw the processes in task manager of the guest os, but nothing appears in the environment variable.
here is the my code:
var userName = "name";
var password = "password";
var programPath = "C:\\Windows\\System32\\cmd.exe";
//var programPath = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
var arguments = "-command \"powershell \"[Environment]::SetEnvironmentVariable('LastPathedDate', ((Get - HotFix | sort installedon)[-1]).InstalledOn, 'User')\"\"";
//var arguments = "-command \"[Environment]::SetEnvironmentVariable('LastPathedDate', ((Get - HotFix | sort installedon)[-1]).InstalledOn, 'User')\"";
RunProgramInGuest(vm, userName, password, programPath, arguments);
private void RunProgramInGuest(VirtualMachine vm, string username, string password, string programPath, string arguments)
{
var auth = new NamePasswordAuthentication()
{
Username = username,
Password = password,
//InteractiveSession = true
};
var moRef = new ManagedObjectReference("guestOperationsProcessManager");
GuestProgramSpec spec = new GuestProgramSpec()
{
ProgramPath = programPath,
Arguments = arguments
};
var guestProcessManager = new GuestProcessManager(vCenter, moRef);
var pid = guestProcessManager.StartProgramInGuest(vm.MoRef, auth, spec);
var result = guestProcessManager.ReadEnvironmentVariableInGuest(vm.MoRef, auth, new string[] { "LastPatchedDate", "TEMP" });
guestProcessManager.TerminateProcessInGuest(vm.MoRef, auth, pid);
}
not sure where the problem is, or there is another way to get execute some script block and get the value?

Finally, I make it run.
The cmd.exe cannot execute the script, but he powershell.exe could.
the arguments which works is :
var arguments = "invoke-command -scriptblock {[Environment]::SetEnvironmentVariable('LastPatchedDate', (((Get-HotFix | sort installedon)[-1]).InstalledOn), 'User')}";
another finding is, when you use cmd.exe, you may need to terminate the process according to your parameter. but the powershell.exe has no need to do this.
guestProcessManager.TerminateProcessInGuest(vm.MoRef, auth, pid);

Related

.NET Core - Download or read a Json file contents from Azure Ubuntu VM

I have a node.js script on my Ubuntu VM, which produces a Json file once it finishes execution. So far I am able to execute this script from a .NET Core console app (using Visual Studio 2019) with the following code:
string subscriptionId = "mySubscriptionId";
string clientId = "myclientId";
string tenantId = "myTenantId";
string clientSecret = "myClientSecret";
var credentials = SdkContext.AzureCredentialsFactory.FromServicePrincipal(clientId, clientSecret, tenantId, AzureEnvironment.AzureGlobalCloud);
var azure = Azure.Configure().Authenticate(credentials).WithSubscription(subscriptionId);
var vm = await azure.VirtualMachines.GetByResourceGroupAsync("MyAzureRg", "MyUbuntuVm");
string cmd1 = "#!/bin/bash";
string cmd2 = "cd /home/Username/myapp/";
string cmd3 = "xvfb-run nodejs myNodeScript.js";
List<string> commands = new List<string>{cmd1,cmd2,cmd3};
Console.WriteLine("Executing shell script");
await vm.RunShellScriptAsync(commands, null);
Console.WriteLine("Script executed!");
I then use PuTTY and SSH to the Ubuntu VM, and can confirm that the Json is properly created in path /home/Username/myapp/. However, my question is how do I get the contents from it back (either read or download) to .NET Core app?
The easiest way to get the json file value in this scenario should be using RunShellScriptAsync() again, pls try the code below :
List<string> commands = new List<string> { "cat <your json file path>" };
var result = vm.RunShellScriptAsync(commands, null).GetAwaiter().GetResult();
var message = result.Value[0].Message;
Console.WriteLine(result.Value[0].DisplayStatus);
var stdout = message.Substring(message.IndexOf("[stdout]") + 9).Replace("[stderr]","").Trim();
Console.WriteLine(stdout);
Result on my side :
Hope it helps .

how to print Test page on a Printer?

I am new to C# and I am using windows forms, windows 7 and .Net 4.0.
I have 3 printers connected to my computer I want to print windows test page on a specific printer. All printers names are listed in ComboBox as shown in the following code I want to select a printer from ComboBox and print test page.
I had a look Here, Here and Here but nothing helped.
Does anyone know how to do it?
foreach (string printer in System.Drawing.Printing.PrinterSettings.InstalledPrinters)
{
comboBox_Printers.Items.Add(printer);
}
Now, this method might look verbose, but I think it's important, when calling WMI methods, that the Management Options and Scopes are defined correctly.
This gives means to correct/adapt the code to specific contexts, when necessary.
Also, the helper methods here can be reused to initialize any other WMI query.
For example, a wrong Impersonation option, will result in an exception (0x80070005: (E_ACCESSDENIED)) when connecting to the WMI Scope or when the WMI query is executed.
A description of the PrintTestPage method parameters:
string PrinterName: the name of a specific Printer or null to use the default Printer.
string MachineName: the name of a Machine in the Network or null to use the LocalMachine name.
The method returns 0 if successful and throws an exception if the Printer is not found.
Sample call to Print a test page using the default printer in the local machine:
var result = PrintTestPage(null, null);
using System.Linq;
using System.Management;
public static uint PrintTestPage(string PrinterName, string MachineName)
{
ConnectionOptions connOptions = GetConnectionOptions();
EnumerationOptions mOptions = GetEnumerationOptions(false);
string machineName = string.IsNullOrEmpty(MachineName) ? Environment.MachineName : MachineName;
ManagementScope mScope = new ManagementScope($#"\\{machineName}\root\CIMV2", connOptions);
SelectQuery mQuery = new SelectQuery("SELECT * FROM Win32_Printer");
mQuery.QueryString += string.IsNullOrEmpty(PrinterName)
? " WHERE Default = True"
: $" WHERE Name = '{PrinterName}'";
mScope.Connect();
using (ManagementObjectSearcher moSearcher = new ManagementObjectSearcher(mScope, mQuery, mOptions))
{
ManagementObject moPrinter = moSearcher.Get().OfType<ManagementObject>().FirstOrDefault();
if (moPrinter is null) throw new InvalidOperationException("Printer not found");
InvokeMethodOptions moMethodOpt = new InvokeMethodOptions(null, ManagementOptions.InfiniteTimeout);
using (ManagementBaseObject moParams = moPrinter.GetMethodParameters("PrintTestPage"))
using (ManagementBaseObject moResult = moPrinter.InvokeMethod("PrintTestPage", moParams, moMethodOpt))
return (UInt32)moResult["ReturnValue"];
}
}
Helper methods:
private static EnumerationOptions GetEnumerationOptions(bool DeepScan)
{
EnumerationOptions mOptions = new EnumerationOptions()
{
Rewindable = false, //Forward only query => no caching
ReturnImmediately = true, //Pseudo-async result
DirectRead = true, //Skip superclasses
EnumerateDeep = DeepScan //No recursion
};
return mOptions;
}
private static ConnectionOptions GetConnectionOptions()
{
ConnectionOptions connOptions = new ConnectionOptions()
{
EnablePrivileges = true,
Timeout = ManagementOptions.InfiniteTimeout,
Authentication = AuthenticationLevel.PacketPrivacy,
Impersonation = ImpersonationLevel.Impersonate
};
return connOptions;
}

Closed TextWriter Exception When Using Certain Strings

I'm trying to implement a function that displays some ActiveDirectory information onto a webpage, however when I try to display certain information (the user's manager) for some reason it started throwing the below error at me when it hadn't done that in the past. Can anyone explain what's going on here or suggest a fix?
I'm not writing anything to the StringBuilder after disposal during the using() block for the PowerShell, so I have no idea why this is suddenly throwing the exception when the manager_name string is calling the GetManager function, which I know returns the correct value based on VisualStudio's debugging tool showing the proper value when it rolls into line 752
PrintADInfo_displayresults.InnerHtml = html_results;
which is just prior to the disposal of all the objects and return outside the function. What is bizarre is that when setting manager_name to the empty string or just "test", the code will run fine but using the GetUsersManager function will cause the exception to fire.
Below you'll find the methods in question and the error text.
Error Text
System.ObjectDisposedException: Cannot write to a closed TextWriter.
at System.IO.__Error.WriterClosed() at
System.IO.StreamWriter.Flush(Boolean flushStream, Boolean
flushEncoder) at System.IO.StreamWriter.Flush() at
System.Management.Automation.Host.TranscriptionOption.Dispose() at
System.Management.Automation.Host.PSHostUserInterface.StopAllTranscribing()
at
System.Management.Automation.Runspaces.LocalRunspace.DoCloseHelper()
at
System.Management.Automation.Runspaces.LocalRunspace.CloseHelper(Boolean
syncCall) at
System.Management.Automation.Runspaces.RunspaceBase.CoreClose(Boolean
syncCall) at
System.Management.Automation.Runspaces.LocalRunspace.Close() at
System.Management.Automation.Runspaces.LocalRunspace.Dispose(Boolean
disposing) at System.Management.Automation.PowerShell.Dispose(Boolean
disposing) at System.Management.Automation.PowerShell.Dispose() at
_Default.PrintADInfo(String username) in h:\Visual Studio 2015\WebSites\WebSite2\Default.aspx.cs:line 753
private void PrintADInfo(string username)
{
//Print AD information for given username
AD_PrintADInfoDiv.Attributes["style"] = "display:block"; //Move to the account display
try
{
using (PowerShell ps = PowerShell.Create())
{
var sb = new StringBuilder();
sb.Append("Grabbing AD info for " + username.ToUpper() + " ...");
//Run command Get-ADUser to get user information from AD
//Command Information -> https://technet.microsoft.com/en-us/library/hh852208(v=wps.630).aspx
// Use -Properties to get specific proprties we want, then use Select to ignore some additional ones
ps.AddScript("Get-ADUser -Properties SamAccountName, CN, EmailAddress, Title, Department, Manager, OfficePhone, Mobile, PasswordLastSet, EmployeeID, co, physicalDeliveryOfficeName " + username + " | select SamAccountName, CN, Title, Department, EmailAddress, Manager, OfficePhone, Mobile, PasswordLastSet, EmployeeID, co, physicalDeliveryOfficeName | ConvertTo-Html");
Collection<PSObject> psOutput = ps.Invoke();
//Add user informtion to StringBuilder for output
foreach (PSObject p in psOutput)
{
sb.Append(p.ToString());
}
//Evil regex hacking
//Quotes are doubled to account for C# literals
//https://stackoverflow.com/questions/9289357/javascript-regular-expression-for-dn
string pattern = #"(?:[A-Za-z][\w-]*|\d+(?:\.\d+)*)=(?:#(?:[\dA-Fa-f]{2})+|(?:[^,=\+<>#;\\""]|\\[,=\+<>#;\\""]|\\[\dA-Fa-f]{2})*|""(?:[^\\""]|\\[,=\+<>#;\\""]|\\[\dA-Fa-f]{2})*"")(?:\+(?:[A-Za-z][\w-]*|\d+(?:\.\d+)*)=(?:#(?:[\dA-Fa-f]{2})+|(?:[^,=\+<>#;\\""]|\\[,=\+<>#;\\""]|\\[\dA-Fa-f]{2})*|""(?:[^\\""]|\\[,=\+<>#;\\""]|\\[\dA-Fa-f]{2})*""))*(?:,(?:[A-Za-z][\w-]*|\d+(?:\.\d+)*)=(?:#(?:[\dA-Fa-f]{2})+|(?:[^,=\+<>#;\\""]|\\[,=\+<>#;\\""]|\\[\dA-Fa-f]{2})*|""(?:[^\\""]|\\[,=\+<>#;\\""]|\\[\dA-Fa-f]{2})*"")(?:\+(?:[A-Za-z][\w-]*|\d+(?:\.\d+)*)=(?:#(?:[\dA-Fa-f]{2})+|(?:[^,=\+<>#;\\""]|\\[,=\+<>#;\\""]|\\[\dA-Fa-f]{2})*|""(?:[^\\""]|\\[,=\+<>#;\\""]|\\[\dA-Fa-f]{2})*""))*)*";
Regex rgx = new Regex(pattern);
//Replace the user's manager field that's normally in CN format to NTID
string manager_name = GetUsersManager(username);
//string manager_name = ""; // <-- Making the manager blank runs fine
string html_results = rgx.Replace(sb.ToString(), manager_name);
PrintADInfo_displayresults.InnerHtml = html_results;
return; // <--- Error gets thrown here
}
}
catch (Exception e)
{
PrintADInfo_displayresults.InnerHtml = "Exception caught:" + e;
return;
}
}
private string GetUsersManager(string username)
{
//Returns the NTID of the user's manager, instead of the CN
string manager_name = "";
using (PowerShell ps = PowerShell.Create())
{
ps.AddScript("(Get-ADUser (Get-ADUser " + username + " -Properties manager).manager).samaccountName");
Collection<PSObject> psOutput = ps.Invoke();
foreach (PSObject p in psOutput)
{
manager_name = manager_name + p.ToString();
}
}
return manager_name;
}
I believe you need to move the call to string manager_name = GetUsersManager(username); to outside of the using (PowerShell ps = PowerShell.Create()) {} block. You have a using inside a using, and I think your object is being disposed prematurely.

How can I get an exit code from WMI spun remote process

I'm executing a process remotely via WMI (Win32_Process Create) but am unable to figure out how I can determine when the process has completed executing. When I first issue the command, there is an exit code (0 for success) but that just tells me the process has been successfully spawned.
Is there a way I can know when the process ends? Thanks!
Faced same issue and wrote a simple VMI wrapper:
var exitStatus = WmiOperations.Run("notepad.exe", wait:10);
Synopsis for Run is:
int Run(string command, // Required
string commandline = null, // (default=none)
string machine = null, // (default=local)
string domain = null, // (default=current user domain)
string username = null, // (default=current user login)
string password = null, // (default=current user password)
SecureString securePassword = null, // (default=current user password)
double wait = double.PositiveInfinity); // (default=wait til command ends);
Source code can be downloaded from here.
Give caesar his due, code is inspired from this one. Simply:
Refactored things to static class
Added more control on remoting parameters
Redesigned event watcher to suppress the unappealing CheckProcess test
Here is an example create on the top of .NET objects but written in Powershell, it's easy to translate it to C#
Clear-Host
# Authentication object
$ConOptions = New-Object System.Management.ConnectionOptions
$ConOptions.Username = "socite\administrateur"
$ConOptions.Password = "adm"
$ConOptions.EnablePrivileges = $true
$ConOptions.Impersonation = "Impersonate"
$ConOptions.Authentication = "Default"
$scope = New-Object System.Management.ManagementScope("\\192.168.183.220\root\cimV2", $ConOptions)
$ObjectGetOptions = New-Object System.Management.ObjectGetOptions($null, [System.TimeSpan]::MaxValue, $true)
# Equivalent to local :
# $proc = [wmiclass]"\\.\ROOT\CIMV2:Win32_Process"
$proc = New-Object System.Management.ManagementClass($scope, "\\192.168.183.220\ROOT\CIMV2:Win32_Process", $ObjectGetOptions)
# Now create the process remotly
$res = $proc.Create("cmd.exe")
# Now create an event to detect remote death
$timespan = New-Object System.TimeSpan(0, 0, 1)
$querryString = "SELECT * From WIN32_ProcessStopTrace WHERE ProcessID=$($res.ProcessID)"
$query = New-Object System.Management.WQLEventQuery ($querryString)
$watcher = New-Object System.Management.ManagementEventWatcher($scope, $query)
$b = $watcher.WaitForNextEvent()
$b

How do you get the UserName of the owner of a process?

I'm trying to get a list of processes currently owned by the current user (Environment.UserName). Unfortunately, the Process class doesn't have any way of getting the UserName of the user owning a process.
How do you get the UserName of the user which is the owner of a process using the Process class so I can compare it to Environment.UserName?
If your solution requires a pinvoke, please provide a code example.
Thanks, your answers put me on the proper path. For those who needs a code sample:
public class App
{
public static void Main(string[] Args)
{
Management.ManagementObjectSearcher Processes = new Management.ManagementObjectSearcher("SELECT * FROM Win32_Process");
foreach (Management.ManagementObject Process in Processes.Get()) {
if (Process["ExecutablePath"] != null) {
string ExecutablePath = Process["ExecutablePath"].ToString();
string[] OwnerInfo = new string[2];
Process.InvokeMethod("GetOwner", (object[]) OwnerInfo);
Console.WriteLine(string.Format("{0}: {1}", IO.Path.GetFileName(ExecutablePath), OwnerInfo[0]));
}
}
Console.ReadLine();
}
}
The CodeProject article How To Get Process Owner ID and Current User SID by Warlib describes how to do this using both WMI and using the Win32 API via PInvoke.
The WMI code is much simpler but is slower to execute. Your question doesn't indicate which would be more appropriate for your scenario.
You will have a hard time getting the username without being an administrator on the computer.
None of the methods with WMI, through the OpenProcess or using the WTSEnumerateProcesses will give you the username unless you are an administrator. Trying to enable SeDebugPrivilege etc does not work either. I have still to see a code that works without being the admin.
If anyone know how to get this WITHOUT being an admin on the machine it is being run, please write how to do it, as I have not found out how to enable that level of access to a service user.
You might look at using System.Management (WMI). With that you can query the Win32_Process tree.
here is the MS link labelled "GetOwner Method of the Win32_Process Class"
Props to Andrew Moore for his answer, I'm merely formatting it because it didn't compile in C# 3.5.
private string GetUserName(string procName)
{
string query = "SELECT * FROM Win32_Process WHERE Name = \'" + procName + "\'";
var procs = new System.Management.ManagementObjectSearcher(query);
foreach (System.Management.ManagementObject p in procs.Get())
{
var path = p["ExecutablePath"];
if (path != null)
{
string executablePath = path.ToString();
string[] ownerInfo = new string[2];
p.InvokeMethod("GetOwner", (object[])ownerInfo);
return ownerInfo[0];
}
}
return null;
}
You'll need to add a reference to System.Management.dll for this to work.
Here's what I ended up using. It works in .NET 3.5:
using System.Linq;
using System.Management;
class Program
{
/// <summary>
/// Adapted from https://www.codeproject.com/Articles/14828/How-To-Get-Process-Owner-ID-and-Current-User-SID
/// </summary>
public static void GetProcessOwnerByProcessId(int processId, out string user, out string domain)
{
user = "???";
domain = "???";
var sq = new ObjectQuery("Select * from Win32_Process Where ProcessID = '" + processId + "'");
var searcher = new ManagementObjectSearcher(sq);
if (searcher.Get().Count != 1)
{
return;
}
var process = searcher.Get().Cast<ManagementObject>().First();
var ownerInfo = new string[2];
process.InvokeMethod("GetOwner", ownerInfo);
if (user != null)
{
user = ownerInfo[0];
}
if (domain != null)
{
domain = ownerInfo[1];
}
}
public static void Main()
{
var processId = System.Diagnostics.Process.GetCurrentProcess().Id;
string user;
string domain;
GetProcessOwnerByProcessId(processId, out user, out domain);
System.Console.WriteLine(domain + "\\" + user);
}
}

Categories