Closed TextWriter Exception When Using Certain Strings - c#

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.

Related

Cmdlet is working on Powershell but it is not working through C#

I want to use a Invoke-RDUserLogoff cmdlet to logoff users on remote computer. This cmdlet works well when is submitted using PowerShell. However, every time I try to execute it using the below code I get a "Invoke-RDUserLogoff is not recognized as the name of a cmdlet.....". Any clue or help will be appreciate it. Thanks!
System.Management.Automation.PowerShell psinstance = System.Management.Automation.PowerShell.Create();
string errorMesg = string.Empty;
//string result = string.Empty;
string command = "";
command = #"Invoke-RDUserLogoff -HostServer " + computerNameTB.Text + " -UnifiedSessionID " + sessionIDTB.Text + " -Force";
psinstance.AddCommand(command);
//Make sure return values are outputted to the stream captured by C#
psinstance.AddCommand("out-string");
PSDataCollection<PSObject> outputCollection = new PSDataCollection<PSObject>();
psinstance.Streams.Error.DataAdded += (object sender1, DataAddedEventArgs e1) =>
{
errorMesg = ((PSDataCollection<ErrorRecord>)sender1)[e1.Index].ToString();
};
//IAsyncResult result = psinstance.BeginInvoke<PSObject, PSObject>(null, outputCollection);
IAsyncResult result = psinstance.BeginInvoke();
//wait for powershell command/script to finish executing
psinstance.EndInvoke(result);
StringBuilder sb = new StringBuilder();
foreach (var outputItem in outputCollection)
{
sb.AppendLine(outputItem.BaseObject.ToString());
}
if (!string.IsNullOrEmpty(errorMesg))
{
MessageBox.Show(errorMesg, "Logoff Users");
}
else
{
MessageBox.Show(sb.ToString(), "LogoffUsers app");
}
A hint I think worth trying is checking the project settings: found few claims that changing the target platform cpu to x64 from x86 might fix locating the proper modules please see: Import-Module does not work in c# powershell command
Good luck!🤞

How can I display specific properties from Active Directory's Attribute Editor, such as 'serialNumber' as string?

I'm running a PowerShell command and filtering for serialNumber. When I run in PowerShell, I get correct output. When I run in Windows Forms app, I get:
#{serialNumber=Microsoft.ActiveDirectory.Management.ADPropertyValueCollection}
Any ideas on how to convert this string into the actual serialNumber?
PowerShell psinstance = PowerShell.Create();
psinstance.AddScript("Get-ADUser " + user.SamAccountName + " -Properties serialNumber | select serialNumber");
var results = psinstance.Invoke();
foreach (var outputItem in results)
{
Debug.WriteLine(outputItem.ToString() + "\n");
}

GuestProcessManager.StartProgramInGuest cannot run powershell block in guest os

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);

Are PowerShell objects returned from a remote session different?

I have a C# application that queries Lync and Exchange PowerShell using Runspaces. If I execute the application on a server that has local access to the PowerShell commandlets i.e. the management tools are installed, it works; however, if I connect to the server remotely, my foreach logic fails with the following error:
Example loop - the first loop works fine, however it fails at the 2nd one as I drill down in the PS objects:
public Collection<PSObject> Workflows;
var wFFilter = _dataService.WorkFlows.ToList();
//foreach (PSObject workflowName in workflowNames)
foreach (dynamic workflowName in wFFilter)
{
var newWorkflow = new WorkFlowViewModel();
if (workflowName != null)
{
//GetDisplay Name
newWorkflow.Name = workflowName.Name;
//Populate IVR options
foreach (dynamic root in workflowName.DefaultAction.Question.AnswerList)
{
if (root.Action.QueueID != null)
{
//Do something
}
}
}
}
This leads me to believe there is something different in the way the PowerShell object is returned. Could this be the case? I just cant figure out why this is different, and how I can handle both local and remotely returned objects.
My PS code:
private RunspacePool rrp;
public Collection<PSObject> ExecuteSynchronously(string PSCommand, string RemoteMachineFqdn, int RemoteMachinePort, string RemoteMachinePath,
bool SslEnabled, string Username, SecureString Password)
{
Collection<PSObject> psResult;
if (rrp == null)
{
string shellUri = #"http://schemas.microsoft.com/powershell/Microsoft.PowerShell";
PSCredential remoteCredential = new PSCredential(Username, Password);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(SslEnabled, RemoteMachineFqdn,
RemoteMachinePort, RemoteMachinePath, shellUri, remoteCredential);
connectionInfo.SkipRevocationCheck = true;
connectionInfo.SkipCACheck = true;
connectionInfo.SkipCNCheck = true;
rrp = RunspaceFactory.CreateRunspacePool(1, 10, connectionInfo);
rrp.Open();
}
using (PowerShell powershell = PowerShell.Create())
{
powershell.RunspacePool = rrp;
powershell.AddScript(PSCommand);
psResult = powershell.Invoke();
}
return psResult;
}
Thanks! Really appreciate some help on this :)
The are different because they've been serialized at the remote session and then deserialized in your local session. Serialization can result in loss of fidelity of object properties, and removal of methods from the objects.

Create SharePoint Site Collection Web Service

I've been attempting to create a site collection from a custom web service I have built in C#. I already had some methods in this that ran some powershell commands so I figured I would just create the site using powershell commands like the code below. This code runs fine without error but does not create a site collection, seems as if it is doing nothing. Is there a better way or can someone see what may be wrong below?
public string CreateSiteCollection(string urlroot, string urlname, string database, string primaryadmin, string secondadmin, string language, string description, string title, string template)
{
//Find language and template code
string lang_code = get_lang_code(language);
string temp_code = get_temp_code(template);
Call the PowerShell.Create() method to create an empty pipeline
PowerShell ps = PowerShell.Create();
// Place our script in the string myscript
string myscript = string.Format(#"
Add-PSSnapin Microsoft.SharePoint.Powershell -erroraction 'silentlycontinue'
$template = Get-SPWebTemplate ""{0}""
New-SPSite {1}{2} -OwnerAlias '{3}' -SecondaryOwnerAlias '{4}' -Language {5} -Description '{6}' -ContentDatabase {7} -Template $template -Name '{8}'
", temp_code, urlroot, urlname, primaryadmin, secondadmin, lang_code, description, database, title);
// Create PowerShell runspace
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline(); // create pipepline then feed it myscript
pipeline.Commands.AddScript(myscript);
pipeline.Commands.Add("Out-String");
Collection<PSObject> results;
try
{
// Executing the script
results = pipeline.Invoke();
}
catch (Exception e)
{
// An error occurred, return the exception
return string.Format("Exception caught: {0}", e);
}
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
string output = stringBuilder.ToString().Trim();
if (output == "")
{
return "PowerShell Commands ran sucessfully with no output";
}
else
{
return String.Format("PowerShell Commands ran sucessfully and returned the following: {0}", output);
}
} // End of CreateSiteColleciton
I may use the Admin.asmx web service instead, but it would be easier if I could get this working because it allows me more customization.
Is there a reason why you can't call the SharePoint server side object model directly from your web service like this, http://msdn.microsoft.com/en-us/library/office/ms411953(v=office.14).aspx.
Or is there a requirement to go through a set of CmdLets?
I decided to go the with the object model just because it makes more sense. Below is the code for creating a new site collection:
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite site = new SPSite("http://servername:port/"))
{
using (SPWeb web = site.OpenWeb())
{
HttpContext.Current = null;
site.AllowUnsafeUpdates = true;
web.AllowUnsafeUpdates = true;
var newSite = site.WebApplication.Sites.Add(....);
}
}
});

Categories