System.outofmemory exception when running Powershell script - visual-studio-2010

I've written VC++ code to run Powershell scripts on a remote server. I create a PSSession to a server first and then I run around 30000 commandlets. While performing this operation, I receive System.outofmemory exception. I've also tried to increase 'MaxMemoryPerShellMB' to 2048, but that doesn't solve the problem. Kindly advise. This is my code:
runSpace = RunspaceFactory::CreateRunspace();
runSpace->Open();
ps = PowerShell::Create();
ps->Runspace = runSpace;
PSCommand^ rmcommand = gcnew PSCommand();
Uri^ uri = gcnew Uri("http://"+ serverName +"/powershell?serializationLevel=Full");
rmcommand->AddCommand("New-PSSession");
rmcommand->AddParameter("ConfigurationName", "Microsoft.Exchange");
rmcommand->AddParameter("ConnectionUri", uri);
rmcommand->AddParameter("Authentication", "kerberos");
rmcommand->AddParameter("Name", "pssession");
PSSessionOption^ sessionOption = gcnew PSSessionOption();
sessionOption->SkipCACheck = true;
sessionOption->SkipCNCheck = true;
sessionOption->SkipRevocationCheck = true;
rmcommand->AddParameter("SessionOption", sessionOption);
ps->Commands = rmcommand;
Collection<PSObject^>^ result = ps->Invoke();
rmcommand = gcnew PSCommand();
rmcommand->AddScript("Set-ExecutionPolicy -ExecutionPolicy RemoteSigned");
ps->Commands = rmcommand;
ps->Invoke();
rmcommand = gcnew PSCommand();
rmcommand->AddScript("Import-PSSession -Session $session -CommandName 'Get-MailboxStatistics'");
ps->Commands = rmcommand;
ps->Invoke();
PSCommand^ command = gcnew PSCommand();
for(size i = 0; i < 30000; i++)
{
mbxidentity=mailbox[i];
command = gcnew PSCommand();
command->AddScript("Get-MailboxStatistics -Identity \'" + mbxidentity + "\' | select LegacyDN");
ps->Commands = command;
Collection<PSObject^>^ result = ps->Invoke();
//Do something with result
}
Here 'mailbox' is an array containing list of mailbox identities. Also, I get Outofmemory at different places. Sometimes I get it when running New-PSSession itself. Sometimes, around 1000 commandlets run successfully and then I get the exception. What might be the problem? I have checked the memory of the remote serve and it is fine.

I'm not very experienced with running powershell code with c++/c#. My comments were getting big, so I'll use an answer to ask questions etc.
Have you restarted the winrm-service on the server(s) after raising
the MaxMemoryPerShellMB?
Why are you running Get-Mailbox? I can't see that you're storing the output anywhere, and this would probably create a lot of objects = memory-usage

Related

WMI CommandLineTemplate Variables Cutting Off

I've been working on a solution to monitor and respond to certain windows services stopping, and I could really use a few hundred extra sets of eyes on this. I'm setting up the WMI subscription in Powershell and the subscription seems to do it's job, but I'm not getting the expected output using the CommandLineTemplate. I'm trying to push the service name, current state, and previous state to a powershell script or executable (same PS script but compiled) but I only get part of the first variable before it cuts off. I've tried formatting the commandlinetemplate multiple different ways, escaping the variables with single/double/escaped quotes, and tried re-ordering the variables, but it always seems to be part of the first and nothing else gets passed. For testing I'm just trying to grab the variables and write them to a log before I move on to the more fun stuff.
Subscription Code:
$instanceFilter = ([wmiclass]"\\.\root\subscription:__EventFilter").CreateInstance()
$instanceFilter.QueryLanguage = "WQL"
$instanceFilter.Query = "select * from __instanceModificationEvent within 5 where targetInstance isa 'win32_Service' AND targetInstance.Name LIKE 'ServiceNamex.%'"
$instanceFilter.Name = "ServiceFilter"
$instanceFilter.EventNamespace = 'root\cimv2'
$result = $instanceFilter.Put()
$newFilter = $result.Path
#Creating a new event consumer
$instanceConsumer = ([wmiclass]"\\.\root\subscription:CommandLineEventConsumer").CreateInstance()
$instanceConsumer.Name = 'ServiceConsumer'
$instanceConsumer.CommandLineTemplate = "C:\Tools\ServiceMonitor.exe `"%TargetInstance.Name%`" `"%TargetInstance.State%`" `"%PreviousInstance.State%`""
$instanceConsumer.ExecutablePath = "C:\Tools\ServiceMonitor.exe"
$result = $instanceConsumer.Put()
$newConsumer = $result.Path
#Bind filter and consumer
$instanceBinding = ([wmiclass]"\\.\root\subscription:__FilterToConsumerBinding").CreateInstance()
$instanceBinding.Filter = $newFilter
$instanceBinding.Consumer = $newConsumer
$result = $instanceBinding.Put()
$newBinding = $result.Path
Target Code (PS1/EXE):
[CmdletBinding()]
param (
[parameter(Position=0)][string]$serviceName = "Error",
[parameter(Position=1)][string]$currentState = "Error",
[parameter(Position=2)][string]$previousState = "Error"
)
Add-Content -path "C:\temp\service.log" -value "$(Get-Date) - The state of $serviceName on $env:Computername has changed from $previousState to $currentState."
If ($currentState -eq "Stopped")
{
Add-Content -path "C:\temp\service.log" -value "$(Get-Date) - Attempting to restart $serviceName."
Start-Service -DisplayName $serviceName
}
Example Output for ServiceNamex.Funct.QA.12 stopping and starting:
10/30/2019 03:52:11 - The state of ServiceNamex.Funct.QA. has changed to Error.
10/30/2019 03:52:16 - The state of ServiceNamex.Funct.QA. has changed to Error.
Chris, It looks like the issue lies in the variables that you're getting from the first script. I'm not sure where you're getting those variables from. The ServiceMonitor.exe application isn't a PS script and I don't see where the subscription calls out to ServiceMonitor.ps1
Just copying your PS lines into a ServiceMonitor.ps1 file, I was able to run the following command and get the subsequent log information.
.\ServiceMonitor.ps1 TestService Running Stopped
11/01/2019 11:01:24 - The state of TestService on 7D25PX1 has changed from Stopped to Running.
11/01/2019 11:13:11 - The state of TestService on 7D25PX1 has changed from Stopped to Running.
11/01/2019 11:13:44 - The state of TestService on 7D25PX1 has changed from Stopped to Running.
Hopefully that helps to at least point you in the right direction. Feel free to respond with more information and I'll be happy to update the post.

Communication between website and windows

This is general question, how to approach this problem.
For my technical degree i would like to do sort of website application that will connect windows machine, send a request to powershell e.g. get-processes, and in the end display it on the website.
I'm not sure if PowerShell Web Access can be modified like that, Is there any other solution?
Like service that i could communicate on?
-mateusz
You can use Powershell runspaces, this is an example, but in your case you might have to change it for the authentication methods in you have to use...
PSCredential credential = new PSCredential(user, secure_pw);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo();
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Credssp;
connectionInfo.ProxyAuthentication = AuthenticationMechanism.Negotiate;
connectionInfo.OperationTimeout = 4 * 60 * 1000; // 4 minutes.
connectionInfo.OpenTimeout = 1 * 60 * 1000; // 1 minute.
connectionInfo.Credential = credential;
Runspace rs = RunspaceFactory.CreateRunspace(connectionInfo);
rs.Open();
using (PowerShell PowerShellInstance = PowerShell.Create())
{
string hostname = "my-host";
PowerShellInstance.Runspace = rs;
PowerShellInstance.AddScript(string.Format("param([string]$hostname) Get-Process -ComputerName $hostname"))
PowerShellInstance.AddParameter("hostname", hostname);
// invoke execution on the pipeline (collecting output)
Collection<PSObject> PSOutput = PowerShellInstance.Invoke();
// do something with the errors found.
if (PowerShellInstance.Streams.Error.Count > 0)
{
foreach (var error in PowerShellInstance.Streams.Error)
{
Console.WriteLine(error.Exception.Message);
}
}
}
rs.Dispose();
If do it this way, I recommend you do a bit of research about PowerShellInstance.AddScript vs PowerShellInstance.AddCommand and how the parameters have to be handled, etc...

Windows Update Agent API - Searching for Updates

I wrote a Powershell script that uses the Windows Update Agent API (IUpdateSearcher, IUpdateDownloader, IUpdateInstaller etc.). Everything works fine, the script finds avaiable updates, downloads and installs them.
However, there is a problem when searching for consecutive updates. For example, there is an update for the .Net Framework 4.5.2. The update is installed by script and the PC is rebooted afterwards. Now there should be an update for the .Net Framework 4.5.2 Language Pack avaiable.
But it is not. At least not via the API. A manual search with the GUI (Windows Update) works.
After the manual search, the API finds the update a well!
What am I missing in my script? I could not find anything in Microsofts documentation: https://msdn.microsoft.com/en-us/library/windows/desktop/aa386868(v=vs.85).aspx
$updateSession = New-Object -ComObject 'Microsoft.Update.Session'
$UpdateSession.WebProxy.AutoDetect = $false
$updateSearcher = $updateSession.CreateUpdateSearcher()
$searchResult = $updateSearcher.Search('IsInstalled=0 and IsHidden=0')
$objCollectionDownload = New-Object -ComObject 'Microsoft.Update.UpdateColl'
foreach ($update in $searchResult.Updates)
{
$objCollectionTmp = New-Object -ComObject 'Microsoft.Update.UpdateColl'
$objCollectionTmp.Add($update) | Out-Null
$downloader = $updateSession.CreateUpdateDownloader()
$downloader.Updates = $objCollectionTmp
try
{
$downloadResult = $downloader.Download()
}
catch
{
//exception Handling
}
$objCollectionDownload.Add($update) | Out-Null
}
$updatesToInstall = New-Object -ComObject 'Microsoft.Update.UpdateColl'
$updateInstaller = $updateSession.CreateUpdateInstaller()
foreach ($update in $objCollectionDownload)
{
//accept Eula etc...
$updatesToInstall.Add($update) | Out-Null
}
$updateInstaller.Updates = $updatesToInstall
$installationRestult = $updateInstaller.Install()
//check installation result
Oddly enough I had the same issue just now, Windows GUI showed a particular update, Our GUI using the API wouldn't show this particular update... I had IsInstalled = 0 and IsHidden = 0.... I looked in the WIndows Update log and found the criteria that the WIndows GUI was using.
IsInstalled=0 and DeploymentAction='Installation' or IsPresent=1 and DeploymentAction='Uninstallation' or IsInstalled=1 and DeploymentAction='Installation' and RebootRequired=1 or IsInstalled=0 and DeploymentAction='Uninstallation' and RebootRequired=1
Added this to my application in place of IsInstalled = 0 and IsHidden = 0 and the update showed straight up :-/ don't really understand why but I am not complaining.

Is there an easy way to check if CredSSP is enabled on a systems?

I am aware of the Get-WSManCredSSP function; however, this cmdlet does not work well in a script. This returns a long string similar to the following:
The machine is configured to allow delegating fresh credentials to the following target(s): wsman/*,wsman/*,wsman/*,wsman/*
This computer is configured to receive credentials from a remote client computer.
I cannot easily include this in a script that I am writing, so I'm looking for an alternative way to check CredSSP.
Can't you consider using this as documented in the CmdLet help: Gets the WS-Management CredSSP setting on the client (<localhost|computername>\Client\Auth\CredSSP).
On a local machine it gives :
(Get-Item WSMan:\localhost\Client\Auth\CredSSP).value
You can use it like this :
(Get-Item WSMan:\localhost\Client\Auth\CredSSP).value -eq $false
You can first test if WinRm is available :
(Get-Service -Name winrm ).Status
I was also struggling with the limitations of the Get-WSManCredSSP output, and found this helper script by Victor Vogelpoel/Ravikanth Chaganti to be really helpful.
Some examples:
Check if current machine has been configured as CredSSP server and/or client:
(Get-WSManCredSSPConfiguration).IsServer
(Get-WSManCredSSPConfiguration).IsClient
Check if a specified client machine has been set up for delegation:
Get-WSManCredSSPConfiguration | % { $_.ClientDelegateComputer.Contains('clientcomputername') }
(not intended as a replacement for the work of Vogelpoel & Chaganti, but as a quick summary of a quick reading of CredSSP.cs, so you can get a quick grasp of what it's doing - that said, it was tested on several systems I had at hand and seems to work)
function Get-WSManCredSSPState
{
$res = [pscustomobject]#{DelegateTo = #(); ReceiveFromRemote = $false}
$wsmTypes = [ordered]#{}
(gcm Get-WSManCredSSP).ImplementingType.Assembly.ExportedTypes `
| %{$wsmTypes[$_.Name] = $_}
$wmc = new-object $wsmTypes.WSManClass.FullName
$wms = $wsmTypes.IWSManEx.GetMethod('CreateSession').Invoke($wmc, #($null,0,$null))
$cli = $wsmTypes.IWSManSession.GetMethod('Get').Invoke($wms, #("winrm/config/client/auth", 0))
$res.ReceiveFromRemote = [bool]([xml]$cli).Auth.CredSSP
$afcPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\CredentialsDelegation\AllowFreshCredentials'
if (test-path $afcPath)
{
$afc = gi $afcPath
$res.DelegateTo = $afc.GetValueNames() | sls '^\d+$' | %{$afc.GetValue($_)}
}
return $res
}

uninstalling applications using SCCM SDK

I have been trying to uninstall applications on devices or users using SCCM. I have been successful in creating an application assignment that would install applications, but I haven't been able to get it to uninstall. The code I have been using is:
IResultObject assignment = this.manager.CreateInstance("SMS_ApplicationAssignment");
IResultObject application =
this.manager.GetInstance("SMS_Application.CI_ID=16777339");
assignment["ApplicationName"].StringValue = application["LocalizedDisplayName"].StringValue;
assignment["AssignedCI_UniqueID"].StringValue = application["CI_UniqueID"].StringValue;
assignment["AssignedCIs"].IntegerArrayValue = new[] { application["CI_ID"].IntegerValue};
assignment["AssignmentName"].StringValue = "Deepak's deployment";
assignment["CollectionName"].StringValue = "Deepak's Collection of Devices";
assignment["DisableMomAlerts"].BooleanValue = true;
assignment["NotifyUser"].BooleanValue = false;
assignment["OfferFlags"].IntegerValue = 0;
assignment["DesiredConfigType"].IntegerValue = 1;
assignment["OverrideServiceWindows"].BooleanValue = false;
assignment["RebootOutsideOfServiceWindows"].BooleanValue = false;
assignment["SuppressReboot"].IntegerValue = 0;
assignment["TargetCollectionID"].StringValue = "UKN0000F";
assignment["EnforcementDeadline"].DateTimeValue = DateTime.Now.AddDays(1);
assignment["StartTime"].DateTimeValue = DateTime.Now;
assignment["UseGMTTimes"].BooleanValue = false;
assignment["UserUIExperience"].BooleanValue = false;
assignment["WoLEnabled"].BooleanValue = false;
assignment["RequireApproval"].BooleanValue = true;
assignment["OfferTypeId"].IntegerValue = 2;
assignment.Put();
This code will put up the application as an install deployment in SCCM. How do I get it as an uninstall deployment?
There is an AppAction enumeration, which I suspect is used by the client and not on the server.
typedef enum AppAction
{
appDiscovery = 0,
appInstall = 1,
appUninstall = 2
} AppAction;
Any help would be appreciated!
The setting that needs to be changed is DesiredConfigType.
For your code add the following before put():
assignment["DesiredConfigType"].IntegerValue = 2;
A value of 1 represents install (required) and 2 will uninstall (not allowed).
https://msdn.microsoft.com/en-us/library/hh949014.aspx
The way I do it is first use uninstall.exe to determine the guid of the program, and then create a program for the package I wish to uninstall, and just call uninstall.exe /whatever as the command. This works for most apps that show up in Add/Remove, and if it doesn't show up there then it'll have to be a hack (or script) anyway to uninstall. I believe the reason you're falling short is because if there is no command to uninstall the deployment in sccm, then it has nothing to run.
After you create an uninstall program, you could just call that deployment from your code, and voila.
As long as the target program that you are trying to use was installed via an MSI (Microsoft Installer) then you can loop through the registry to find your product (Registry Location: "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall") And just look at each DisplayName value.
In our environment, I accomplish this task by using Powershell, and we setup a program that specifically uninstalls whatever we are after.
Hope this helps...
Raged.

Resources