I have a code like this:
$powerSchemes = powercfg /l | ForEach-Object {
if ($_ -match 'Power Scheme GUID:\s*([-0-9a-f]+)\s*\(([^)]+)\)\s*(\*)?') {
[PsCustomObject]#{
GUID = $matches[1]
SchemeName = $matches[2] -eq 'Ultimate Performance'
Active = $matches[3]
}
}
}
$customScheme = $powerSchemes | Where-Object { $_.SchemeName -eq 'Ultimate Performance' }
try {
if (!$customScheme.Active) {
powercfg /s $($customScheme.GUID)
}
} catch {
powercfg -duplicatescheme e9a42b02-d5df-448d-aa00-03f14749eb61
if (!$customScheme.Active) {
powercfg /s $($customScheme.GUID)
}
}
I make a try{} cause I know not everyone going to have Ultimate Performance powerplan. But it give me error
powercfg : Invalid Parameters -- try "/?" for help
At C:\Users\MyWin\Desktop\PowerPlan.ps1:15 char:3
+ powercfg /s $($customScheme.GUID)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (Invalid Parameters -- try "/?" for help:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
isn't try{} going ignore
try {
if (!$customScheme.Active) {
powercfg /s $($customScheme.GUID)
}
and just jump to the catch{}??
Sorry I'm a newbie though, if you need more information about the code, just tell me
reason I didnt put
powercfg -duplicatescheme e9a42b02-d5df-448d-aa00-03f14749eb61
if (!$customScheme.Active) {
powercfg /s $($customScheme.GUID)
on try{} so it didn't keep making new powerplan, if theres already Ultimate Performance powerplan, then just active it
*** UPDATED ***
Actually you don't even need a try/catch block:
Clear-Host
$TestScheme = 'Ultimate Performance'
$powerSchemes = powercfg /l | ForEach-Object {
if ($_ -match 'Power Scheme GUID:\s*([-0-9a-f]+)\s*\(([^)]+)\)\s*(\*)?') {
[PsCustomObject]#{
GUID = $matches[1]
SchemeName = $matches[2]
Active = $matches[3] -eq '*'
}
}
}
$powerschemes #*** For Debugging Only ***
$customScheme = $powerSchemes | Where-Object { $_.SchemeName -eq "$TestScheme" }
$ErrorActionPreference = "SilentlyContinue" #*** For Debugging Only ***
"`nCustom Scheme: $($customScheme.SchemeName) `n" #*** For Debugging Only ***
$ErrorActionPreference = "Continue" #*** For Debugging Only ***
If ($Null -eq $CustomScheme) {
"Power scheme named: $TestScheme does NOT Exist!"
}
Else {
If ($customScheme.Active) {
"$TestScheme scheme ACTIVE!"
}
Else {
"$TestScheme Not Active"
}
} #End Else
Test this code on my Dell XPS8920 which has an Ultimate Performance scheme.
I've completely rewritten the code and I think it covers the bases. Note: that I changed your Custom PSObject logic to keep the name and indicate the status in the Active property.
Here are the test results with debugging information included, you can delete the code marked as such for production.
GUID SchemeName Active
---- ---------- ------
381b4222-f694-41f0-9685-ff5bb260df2e Balanced False
49ef8fc0-bb7f-488e-b6a0-f1fc77ec649b Dell False
831878fb-3408-4f4b-a88c-72fdbe2ced67 Ultimate Performance True
8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c High performance False
a1841308-3541-4fab-bc81-f71556f20b4a Power saver False
a7573aee-d201-4451-bf29-7165c6858f5c winword False
f92ea615-6000-45ae-96b2-cb6c364f0ae4 MRBackup False
fce6c371-318d-4bc2-b28e-6425737e2eef Samsung High Performance False
Custom Scheme: Ultimate Performance
Ultimate Performance scheme ACTIVE!
GUID SchemeName Active
---- ---------- ------
381b4222-f694-41f0-9685-ff5bb260df2e Balanced False
49ef8fc0-bb7f-488e-b6a0-f1fc77ec649b Dell False
831878fb-3408-4f4b-a88c-72fdbe2ced67 Ultimate Performance True
8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c High performance False
a1841308-3541-4fab-bc81-f71556f20b4a Power saver False
a7573aee-d201-4451-bf29-7165c6858f5c winword False
f92ea615-6000-45ae-96b2-cb6c364f0ae4 MRBackup False
fce6c371-318d-4bc2-b28e-6425737e2eef Samsung High Performance False
Custom Scheme: Ultimate Performance
Ultimate Performance scheme ACTIVE!
GUID SchemeName Active
---- ---------- ------
381b4222-f694-41f0-9685-ff5bb260df2e Balanced False
49ef8fc0-bb7f-488e-b6a0-f1fc77ec649b Dell False
831878fb-3408-4f4b-a88c-72fdbe2ced67 Ultimate Performance False
8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c High performance True
a1841308-3541-4fab-bc81-f71556f20b4a Power saver False
a7573aee-d201-4451-bf29-7165c6858f5c winword False
f92ea615-6000-45ae-96b2-cb6c364f0ae4 MRBackup False
fce6c371-318d-4bc2-b28e-6425737e2eef Samsung High Performance False
Custom Scheme:
Power scheme named: Test does NOT Exist!
Since the SchemeNames returned by powercfg are LOCALIZED, you should not compare against that, but always use the GUID.
In your case, you want to activate the Ultimate Performance scheme, which is not enabled by default and therefore chances are powercfg /l does not show this scheme.
Try
$desiredScheme = 'e9a42b02-d5df-448d-aa00-03f14749eb61' # we want to activate Ultimate Performance
$powerSchemes = powercfg /l | ForEach-Object {
if ($_ -match 'Power Scheme GUID:\s*([-0-9a-f]+)\s*\(([^)]+)\)\s*(\*)?') {
[PsCustomObject]#{
GUID = $matches[1]
SchemeName = $matches[2]
Active = $matches[3] -eq '*' # --> $true or $false
}
}
}
# GUID SchemeName Active
# ---- ---------- ------
# 381b4222-f694-41f0-9685-ff5bb260df2e Balanced False
# 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c High performance True
# a1841308-3541-4fab-bc81-f71556f20b4a Power saver False
# e9a42b02-d5df-448d-aa00-03f14749eb61 Ultimate Performance False
# NOTE
# -------------------------------------------------------------------------------------------
# 1) The Ultimate Performance scheme may not appear here because it is not enabled by default
# 2) The SchemeName is LOCALIZED, like in Dutch NL pc 'Power saver' is 'Energiebesparing' etc.
# This means you should not compare on the SchemeName, but use the GUID instead
$currentScheme = $powerSchemes | Where-Object { $_.GUID -eq $desiredScheme }
if (!$currentScheme) {
# scheme 'Ultimate Performance' not found, so enable it first
powercfg /duplicatescheme $desiredScheme
# and set it active
powercfg /s $desiredScheme
}
elseif (!$currentScheme.Active) {
powercfg /s $desiredScheme # or use: $currentScheme.GUID
}
If after this you want to check if the power scheme has been set to Ultimate Performance, do
# test if the setting is as desired (just compare GUIDs)
$currentScheme = (powercfg.exe /GETACTIVESCHEME) -replace '.*GUID:\s*([-a-f0-9]+).*', '$1'
if ($currentScheme -eq $desiredScheme) {
Write-Host "Current Power plan Settings: $($desiredScheme.SchemeName).!"
}
else {
# exit the script here???
Throw "Power plan Settings did not change to $($desiredScheme.SchemeName).!"
}
Related
function Show-Menu { #Create the Show-Menu function
param ([string]$Title = 'Functions') #Sets title
Clear-Host
Write-Host "`t6: Reboot History." -foregroundcolor white
Write-Host "`tQ: Enter 'Q' to quit."
} #close of create show menu function
#Begin Main Menu
do
{
Show-Menu #Displays created menu above
$Selection = $(Write-Host "`tMake your selection: " -foregroundcolor Red -nonewline; Read-Host)
switch ($selection) #Begin switch selection
{
#===Reboot History===
'6' {
$Workstation = $(Write-Host "Workstation\IP Address" -nonewline -foregroundcolor DarkGreen) + $(Write-Host "(Use IP for remote users)?: " -NoNewline; Read-Host)
$DaysFromToday = Read-Host "How many days would you like to go back?"
$MaxEvents = Read-Host "How many events would you like to view?"
$EventList = Get-WinEvent -ComputerName $Workstation -FilterHashtable #{
Logname = 'system'
Id = '41', '1074', '1076', '6005', '6006', '6008', '6009', '6013'
StartTime = (Get-Date).AddDays(-$DaysFromToday)
} -MaxEvents $MaxEvents -ErrorAction Stop
foreach ($Event in $EventList) {
if ($Event.Id -eq 1074) {
[PSCustomObject]#{
TimeStamp = $Event.TimeCreated
Event = $Event.Id
ShutdownType = 'Restart'
UserName = $Event.Properties.value[6]
}
}
if ($Event.Id -eq 41) {
[PSCustomObject]#{
TimeStamp = $Event.TimeCreated
Event = $Event.Id
ShutdownType = 'Unexpected'
UserName = ' '
}
}
}
pause
}
}
}
until ($selection -eq 'q') #End of main menu
Works perfectly fine if I remove the script from the switch and run it separately, but as soon as I call it from the switch it still asks for the workstation/IP, how many days, and max events, but just outputs nothing.
Here is what it looks like when it works:
How many days would you like to go back?: 90
How many events would you like to view?: 999
TimeStamp Event ShutdownType UserName
--------- ----- ------------ --------
12/23/2022 12:20:55 AM 1074 Restart Username
12/20/2022 1:00:01 AM 1074 Restart Username
12/17/2022 12:21:54 AM 1074 Restart Username
12/13/2022 8:57:40 AM 1074 Restart Username
This is what I get when I run it within the switch menu
Workstation\IP Address(Use IP for remote users)?: IP Address
How many days would you like to go back?: 90
How many events would you like to view?: 999
Press Enter to continue...:
I have tried just doing 1 day and 1 event, but same results. No errors or anything indicating a failure, so not sure how to troubleshoot this. I have had similar issues with switches in the past that were resolved with some researching into scopes, but I don't think this is the same case as it is all self contained within the switch itself.
I am at a loss, any ideas? As always, any insight into my script is greatly appreciated, even if it doesn't resolve the problem at hand.
JosefZ has provided the crucial pointer:
force synchronous to-display output with, such as with Out-Host
if you neglect to do so, the pause statement will - surprisingly - execute before the [pscustomobject] instances emitted by the foreach statement, due to the asynchronous behavior of the implicitly applied Format-Table formatting - see this answer for details.
Here's a simplified example:
switch ('foo') {
default {
# Wrap the `foreach` statement in . { ... },
# so its output can be piped to Out-Host.
. {
foreach ($i in 1..3) {
[pscustomobject] #{ prop = $i }
}
} |
Out-Host # Without this, "pause" will run FIRST.
pause
}
}
Note:
For Out-Host to format all output together it must receive all output from the foreach loop as part of a single pipeline.
Since foreach is a language statement (rather than a command, such as the related ForEach-Object cmdlet) that therefore cannot directly be used at the start of a pipeline, the above wraps it in a script block ({ ... }) that is invoked via ., the dot-sourcing operator, which executes the script block directly in the caller's context and streams the output to the pipeline.
This limitation may be surprising, but is rooted in the fundamentals of PowerShell's grammar - see GitHub issue #10967.
An all-pipeline alternative that doesn't require the . { ... } workaround would be:
1..3 |
ForEach-Object {
[pscustomobject] #{ prop = $_ } # Note the automatic $_ var.
} |
Out-Host
I am writing a script that checks for a registry key property created by a Bitlocker GPO, checks to see if the device is already protected and if the key is present and the device is not protected then encrypts the device.
I've tested each of these functions individually and they all work, and they all seem to work in the context of the script, outputting the right loop, however when I run the whole script at once, the only loop that DOESN'T work is the loop where the encrypting function runs.
I have the exit codes here because I am using Manage Engine to push out the script to a few remote devices around our estate and I would like to be able to see easily where the script is failing so I can deploy something else.
# FUNCTION - Function created to check existence of Active Directory GPO
Function Test-RegistryValue {
$regPath = 'HKLM:\SOFTWARE\Policies\Microsoft\FVE'
$regPropertyName = 'ActiveDirectoryBackup'
# Value of registry key ported into a script-level variable accessible from outside the function
$regValue = (Get-ItemPropertyValue -Path $regPath -Name $regPropertyName -ErrorAction ignore)
if ($regValue -eq 1)
{
$script:regExit = 0
}
# If the key exists but is not set to write back
elseif ($regValue -eq 0)
{
$script:regExit = 1
}
# If the registry property doesn't exist
else
{
$script:regExit = 5
}
}
# FUNCTION - Function tests if BitLocker protection is already on
function Test-TBBitlockerStatus {$BLinfo = Get-Bitlockervolume
if($blinfo.ProtectionStatus -eq 'On' -and $blinfo.EncryptionPercentage -eq '100')
{
return $true
}
else
{
return $false
}
}
# FUNCTION - This function is the actual encryption script
Function Enable-TBBitlocker {
Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -RecoveryPasswordProtector
$RecoveryKey = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector | Where-Object {$_.KeyProtectorType -eq "RecoveryPassword"}
Manage-bde -protectors -adbackup $env:SystemDrive -id $RecoveryKey[0].KeyProtectorId
Enable-BitLocker -MountPoint $env:SystemDrive -UsedSpaceOnly -TpmProtector -SkipHardwareTest
}
# MAIN SCRIPT
# Running the GPO check function
Test-RegistryValue
Test-TBBitlockerStatus
$regExit
if ($regExit -eq 1)
{
Write-Host 'Key present but writeback not enabled.'
exit 51
}
# Exit if GPO not present
elseif ($regExit -eq 5)
{
Write-Host 'GPO not present.'
exit 55
}
# Test for device already being encrypted
elseif ((Test-TBBitlockerStatus -eq True) -and ($regExit -eq 0))
{
Write-Host 'Writeback enabled but device already protected.'
exit 61
}
# Test for device being encrypted and writeback is not enabled.
elseif ((Test-TBBitlockerStatus -eq True) -and ($regExit -ge 1))
{
Write-Host 'Device already protected but AD writeback is not enabled.'
exit 65
}
elseif ((Test-TBBitlockerStatus -eq False) -and ($regExit -eq 0))
{
Enable-TBBitlocker | Write-Output
Write-Host 'Encryption successful.'
exit 10
}
The Write-Host comments are so I can see where it's failing or succeeding. If I manually change the key value to 1 or 0, or encrypt the device and run the script the correct loop runs, however when the device is not encrypted and has the key in the registry, it does absolutely nothing. The exit code doesn't change, there's no output to the host.
I have the output included before the main if scope to see if the values are being set correctly, which they are but that final encryption function loop does not run at all.
I have written below PowerShell script to get the ping results of server and it also generates the log for the same. My script works fine, also generates log but it take so much time as there are many servers. Is there any way I can quickly get the results. I have tried start-job but that generates error.
$logpath = $logpath +"\" +$Logname
Function Log($txt)
{
if (! (Test-Path $logpath)) {
New-Item $logpath |Out-Null
}
$date = Get-Date -Format "dd_MMM_yyyy_hh:mm:ss - "
$txt1 = ($date + $txt)
Add-Content "$logpath" "$txt1"
Add-Content "$logpath" " "
}
$ServerDetails=import-csv $Servercsv
foreach($servertest in $ServerDetails)
{
if((Test-Connection -ComputerName $servertest.servers -Count 2))
{
Log("'" + $servertest.servers + "' Successfully started operation")
Write-Host "Started Operation Successfully"
}
if(!(Test-Connection -ComputerName $servertest.servers -Count 2))
{
Log("'" + $servertest.servers + "'Servers are not pinging")
Write-Host "Servers are not pinging"
}
}
Assumed your "log" function works as expected you could make the rest of your code a little more efficient:
$ServerList = import-csv $Servercsv
foreach ($ComputerName in $ServerList) {
$Connection = Test-Connection -ComputerName $ComputerName.servers -Count 1 -Quiet
if ($Connection) {
Log("'" + $ComputerName.servers + "' Successfully started operation")
Write-Host "Started Operation Successfully"
}
Else {
Log("'" + $ComputerName.servers + "'Servers are not pinging")
Write-Host "Servers are not pinging"
}
}
For the speed effort for pinging, try this one out and compare speeds relative to how you are doing/getting from yours.
Final Super-Fast Ping Command
function Test-OnlineFast
{
param
(
# make parameter pipeline-aware
[Parameter(Mandatory,ValueFromPipeline)]
[string[]]
$ComputerName,
$TimeoutMillisec = 1000
)
begin
{
# use this to collect computer names that were sent via pipeline
[Collections.ArrayList]$bucket = #()
# hash table with error code to text translation
$StatusCode_ReturnValue =
#{
0='Success'
11001='Buffer Too Small'
11002='Destination Net Unreachable'
11003='Destination Host Unreachable'
11004='Destination Protocol Unreachable'
11005='Destination Port Unreachable'
11006='No Resources'
11007='Bad Option'
11008='Hardware Error'
11009='Packet Too Big'
11010='Request Timed Out'
11011='Bad Request'
11012='Bad Route'
11013='TimeToLive Expired Transit'
11014='TimeToLive Expired Reassembly'
11015='Parameter Problem'
11016='Source Quench'
11017='Option Too Big'
11018='Bad Destination'
11032='Negotiating IPSEC'
11050='General Failure'
}
# hash table with calculated property that translates
# numeric return value into friendly text
$statusFriendlyText = #{
# name of column
Name = 'Status'
# code to calculate content of column
Expression = {
# take status code and use it as index into
# the hash table with friendly names
# make sure the key is of same data type (int)
$StatusCode_ReturnValue[([int]$_.StatusCode)]
}
}
# calculated property that returns $true when status -eq 0
$IsOnline = #{
Name = 'Online'
Expression = { $_.StatusCode -eq 0 }
}
# do DNS resolution when system responds to ping
$DNSName = #{
Name = 'DNSName'
Expression = { if ($_.StatusCode -eq 0) {
if ($_.Address -like '*.*.*.*')
{ [Net.DNS]::GetHostByAddress($_.Address).HostName }
else
{ [Net.DNS]::GetHostByName($_.Address).HostName }
}
}
}
}
process
{
# add each computer name to the bucket
# we either receive a string array via parameter, or
# the process block runs multiple times when computer
# names are piped
$ComputerName | ForEach-Object {
$null = $bucket.Add($_)
}
}
end
{
# convert list of computers into a WMI query string
$query = $bucket -join "' or Address='"
Get-WmiObject -Class Win32_PingStatus -Filter "(Address='$query') and timeout=$TimeoutMillisec" |
Select-Object -Property Address, $IsOnline, $DNSName, $statusFriendlyText
}
}
Test-OnlineFast -ComputerName google.de, powershellmagazine.com, 10.10.10.200, 127.0.0.1
<#
# Results
Address Online DNSName Status
------- ------ ------- ------
127.0.0.1 True DESKTOP-7AAMJLF Success
google.de True google.de Success
powershellmagazine.com True powershellmagazine.com Success
10.10.10.200 False Request Timed Out
#>
Test-Connection with an array of targetnames and the asjob parameter is fast (powershell 5.1). Unresponsive addresses have a null responsetime. Annoyingly, the property names don't match the column headings.
$list = 'microsoft.com','yahoo.com'
$results = test-connection $list -count 1 -asjob | receive-job -wait
$results | select address,responsetime
address responsetime
------- ------------
yahoo.com 39
microsoft.com
I was trying to use Powershell script to set up windows 10 mobile hotspot (which is the function in "Settings" -> "Network & Internet" -> "Mobile hotspot"). I was able to turn it on through the following script:
[Windows.System.UserProfile.LockScreen,Windows.System.UserProfile,ContentType=WindowsRuntime] | Out-Null
# Define functions. Not important to this question
Add-Type -AssemblyName System.Runtime.WindowsRuntime
$asTaskGeneric = ([System.WindowsRuntimeSystemExtensions].GetMethods() | ? { $_.Name -eq 'AsTask' -and $_.GetParameters().Count -eq 1 -and $_.GetParameters()[0].ParameterType.Name -eq 'IAsyncOperation`1' })[0]
Function Await($WinRtTask, $ResultType) {
$asTask = $asTaskGeneric.MakeGenericMethod($ResultType)
$netTask = $asTask.Invoke($null, #($WinRtTask))
$netTask.Wait(-1) | Out-Null
$netTask.Result
}
Function AwaitAction($WinRtAction) {
$asTask = ([System.WindowsRuntimeSystemExtensions].GetMethods() | ? { $_.Name -eq 'AsTask' -and $_.GetParameters().Count -eq 1 -and !$_.IsGenericMethod })[0]
$netTask = $asTask.Invoke($null, #($WinRtAction))
$netTask.Wait(-1) | Out-Null
}
# Create tethering manager
$connectionProfile = [Windows.Networking.Connectivity.NetworkInformation,Windows.Networking.Connectivity,ContentType=WindowsRuntime]::GetInternetConnectionProfile()
$tetheringManager = [Windows.Networking.NetworkOperators.NetworkOperatorTetheringManager,Windows.Networking.NetworkOperators,ContentType=WindowsRuntime]::CreateFromConnectionProfile($connectionProfile)
# Create configuration
$configuration = new-object Windows.Networking.NetworkOperators.NetworkOperatorTetheringAccessPointConfiguration
$configuration.Ssid = "test"
$configuration.Passphrase = "12345678"
# ===================== My question is here =====================
[enum]::GetValues([Windows.Networking.NetworkOperators.TetheringWiFiBand])
$configuration | Get-Member
# ===============================================================
# Check whether Mobile Hotspot is enabled
$tetheringManager.TetheringOperationalState
# Set Hotspot configuration
AwaitAction ($tetheringManager.ConfigureAccessPointAsync($configuration))
# Start Mobile Hotspot
Await ($tetheringManager.StartTetheringAsync()) ([Windows.Networking.NetworkOperators.NetworkOperatorTetheringOperationResult])
but I could only set the SSID and the Passphrase of the network but not the network band which we can choose between '5 GHz', '2.4 GHz' or 'Any available' in the setting gui.
I saw on this post (https://blogs.windows.com/windowsdeveloper/2019/09/10/windows-10-sdk-preview-build-18975-available-now/#DwOj8B0wPu5zd9hK.97) that it seems there is an enum 'TetheringWiFiBand' to set that starting from Windows 10 SDK, version 1903, build 18362 (it's exactly my windows version). However, as you can see, in the middle of my script, when I tried to access this enum, I got an error:
Unable to find type [Windows.Networking.NetworkOperators.TetheringWiFiBand].
Also, there is no such an enum when I print the member of Windows.Networking.NetworkOperators.NetworkOperatorTetheringAccessPointConfiguration
Does anyone have an idea of how to set the wifi band of the mobile hotspot based on this method? Thanks.
For anyone who might be interested, the property of setting the tethering band has been added in WinRT Build 19041
(See: https://learn.microsoft.com/en-us/uwp/api/windows.networking.networkoperators.networkoperatortetheringaccesspointconfiguration?view=winrt-19041
and
https://learn.microsoft.com/en-us/uwp/api/windows.networking.networkoperators.tetheringwifiband?view=winrt-19041)
So now you can add a line in the script posted in the question:
$configuration.Band = 1
to specify the tethering band to 2.4 GHz.
I'm experiencing a strange issue with our script server being overloaded and running out of resources. We have a script that copies data from one location to another, this is defined in a large input file that contains over 200 lines of text in the format 'Source path, Destination path'.
We are now in the process of trying to throttle the maximum jobs we kick of at once and I think it's working fine. But for some reason or another we're still running out of resources on the server when the input file contains over 94 lines. This became apparent after some testing.
We tried to upgrade our Windows 2008 R2 server with PowerShell 4.0 to 4 processors and 8 GB of RAM, but no luck. So I assume my throttling isn't working as designed.
Error code:
Insufficient system resources exist to complete the requested service.
The code:
$MaxThreads = 4
$FunctionFeed = Import-Csv -Path $File -Delimiter ',' -Header 'Source', 'Destination'
$Jobs=#()
Function Wait-MaxRunningJobs {
Param (
$Name,
[Int]$MaxThreads
)
Process {
$Running = #($Name | where State -eq Running)
while ($Running.Count -ge $MaxThreads) {
$Finished = Wait-Job -Job $Name -Any
$Running = #($Name | where State -eq Running)
}
}
}
$ScriptBlock = {
Try {
Robocopy.exe $Using:Line.Source $Using:Line.Destination $Using:Line.File /MIR /Z /R:3 /W:15 /NP /MT:8 | Out-File $Using:LogFile
[PSCustomObject]#{
Source = if ($Using:Line.Source) {$Using:Line.Source} else {'NA'}
Target = if ($Using:Line.Destination) {$Using:Line.Destination} else {'NA'}
}
}
Catch {
"Robocopy | ERROR: $($Error[0].Exception.Message)" |
Out-File -LiteralPath $Using:LogFile
throw $($Error[0].Exception.Message)
}
}
ForEach ($Line in $FunctionFeed) {
$LogParams = #{
LogFolder = $LogFolder
Name = $Line.Destination + '.log'
Date = 'ScriptStartTime'
Unique = $True
}
$LogFile = New-LogFileNameHC #LogParams
' ' >> $LogFile # Avoid not being able to write to log
$Jobs += Start-Job -Name RoboCopy -ScriptBlock $ScriptBlock
Wait-MaxRunningJobs -Name $Jobs -MaxThreads $MaxThreads
}
if ($Jobs) {
Wait-Job -Job $Jobs
$JobResults = $Jobs | Receive-Job
}
Am I missing something here? Thank you for your help.
You're using background jobs, which actually run in remote sessions on the local machine. Remote sessions are intentionally resource restricted, according to settings set in the session configuration. You can check the current settings using
Get-PSSessionConfiguration
And adjust the settings to increase the resources available to the sessions with
Set-PSSessionConfiguration
You may need to do some testing to determine exactly what resource limit you're hitting, and what adjustments need to be made for this particular application to work.
Fixed the problem by enlarging the MaxMemoryPerShellMB for remote sessions from 1GB to 2 GB as described here. Keep in mind that Start-Job is using a remote PowerShell session as mjolinor already indicated, so this variable is applicable to PowerShell jobs.
Solution:
# 'System.OutOfMemoryException error message' when running Robocopy and over 94 PowerShell-Jobs:
Get-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB # Default 1024
Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 2048
# Set PowerShell plugins memory from 1 GB to 2 GB
Get-Item WSMan:\localhost\Plugin\Microsoft.PowerShell\Quotas\MaxMemoryPerShellMB # Default 1024
Set-Item WSMan:\localhost\Plugin\Microsoft.PowerShell\Quotas\MaxMemoryPerShellMB 2048
Restart-Service winrm