Quick tip: change ConfigMgr updates maximum run time

With the new servicing model being applied to pre-Windows 10 operating systems, it’s a good idea to change the default “maximum run time” of the monthly cumulative updates.
For example, I’ve:
configmgr-max-run-time

Here’s a way to achieve this easily with PowerShell.
The -Name parameter of the Get-CMSoftwareUpdate accepts wildcards although its documentation says the opposite.
The -Fast is explained in the following warning message:

WARNING: ‘Get-CMSoftwareUpdate’ supports -Fast for retrieving objects without loading lazy properties. Loading lazy properties can cause significant performance penalties. If it is not necessary to utilize the lazy properties in the returned object(s), -Fast should be used. This warning can be disabled by setting $CMPSSuppressFastNotUsedCheck = $true.

# 1. View what updates will be targeted:
Get-CMSoftwareUpdate -Fast -Name "*Security Monthly Quality Rollup for Windows*" |
Select Max*,*DisplayName
#NB: MaxExecutionTime is in seconds in this case

# 2. Set the maximum run time to 120 minutes
Get-CMSoftwareUpdate -Fast -Name "*Security Monthly Quality Rollup for Windows*"  | 
Set-CMSoftwareUpdate -MaximumExecutionMins 120 -Verbose

Trigger ConfigMgr client actions

A few weeks ago, I came across the following article on refreshing the System Center Configuration Management client

Whenever I see a script, I always wonder, could it be done with a oneliner ?
sms_client-actions-01

As long as the script doesn’t care about the order of the ConfigMgr actions to be performed, yes, it’s achievable this way:

(New-Object -COM 'CPApplet.CPAppletMgr').GetClientActions() | 
Where { 
    $_.Name -match "(\sPolicy$)|(Collection\sCycle$)|(Updates)"
} | ForEach-Object {
    try {
        $_.PerformAction()
        Write-Verbose -Message "Successfully executed $($_.Name)" -Verbose
    } catch {
        Write-Warning -Message "Failed to execute $($_.Name)"
    }
}

I’ve even added some filtering on action names and error handling.

sms_client-actions-02
There’s always more than one way to skin a cat with PowerShell 😉

Quick tip: subnet and ConfigMgr boundary for Direct Access clients

I’ve seen this morning the following blog post about boundaries in Configuration Manager for Direct Access clients.

I’d like to add more info on this topic because I’ve done the same in my environment a few days ago.

Gerry Hampson shows that he gets the ipv6 prefix directly in the properties of a Configuration Managment client in the ConfigMgr Admin console.
He made here an assumption. Having IPv6 addresses reported in Configuration Manager assumes that you have an IPv6 based DNS server where AAAA records are created for your Direct Access clients.
When you don’t have an IPv6 DNS server, you don’t have this info in the ConfigMgr client properties:

Where do I get the client ipv6 prefix ?

You can get it on your Direct Access server(s) with the following cmdlet

Get-RemoteAccess | select  ClientIPv6Prefix

Easy, isn’t it? 😎

Don’t forget! It may seem pretty obvious but…
Before adding the IPv6 prefix as a ConfigMgr boundary, add it first to your Active Directory Sites and Services subnets.
This way your Direct Access clients will immediately know with what Global Catalog and Domain controllers they should talk to.

LogParser vs. PowerShell

The following article popped-up this morning about How to extract NETBIOS name from BADMIF files on ConfigMgr 2012 using Log Parser 2.2

Hey, why would I need the old school log parser from 2005 when we have PowerShell nowadays and then another tool to manipulate the data output?

You can actually achieve this task with a PowerShell one-liner and even without having to manipulate the output.

Get-ChildItem -Path "C:\Program Files\Microsoft Configuration Manager\inboxes\auth\dataldr.box\BADMIFS" -Include *.MIF -Recurse -Force -ErrorAction SilentlyContinue | ForEach-Object { 
    try {
        (
            Get-Content -ReadCount 1 -TotalCount 6 -Path $_.FullName -ErrorAction Stop  | 
            Select-String -Pattern "//KeyAttribute<NetBIOS\sName><(?<ComputerName>.*)>" -ErrorAction Stop 
        ).Matches.Groups[-1].Value 
    } catch {
        Write-Warning -Message "Failed" 
    }
}

If you’ve got warnings, you may also want to investigate which files caused it.
In this case, I propose the following modification of the above code to be able to catch these files.

$ConfigMgrBoxPath = "C:\Program Files\Microsoft Configuration Manager\inboxes\auth\dataldr.box\BADMIFS"
Get-ChildItem -Path $ConfigMgrBoxPath -Include *.MIF -Recurse -Force -ErrorAction SilentlyContinue | ForEach-Object { 
    $File = $_.FullName ;
    try {
        (
            Get-Content -ReadCount 1 -TotalCount 6 -Path $_.FullName -ErrorAction Stop  | 
            Select-String -Pattern "//KeyAttribute<NetBIOS\sName><(?<ComputerName>.*)>" -ErrorAction Stop 
        ).Matches.Groups[-1].Value 
    } catch {
        Write-Warning -Message "Failed for $File" 
    }
}

PowerShell rocks, no doubt! 😎

WMI error 0x8004106C: Quota violation(while running queries)

I’ve encountered this error on my old System Center Configuration Manager 2007 while I was running intensive WMI queries.

The error message on quota violation is raised because the WMI provider of the operating system (Windows 2003) had its amount of private memory that can be held by each host set to a 128MB limit.

To fix this, I set to 512MB which is now the default on Windows 2008/2008R2/2012.

I think that at that time I followed the guidance that you can find in the following knowledge base article: KB2404366

To view the settings of the WMI provider, you can query the __ProviderHostQuotaConfiguration WMI class:

$WMIHT = @{            
 NameSpace=  'root'            
 Class = '__ProviderHostQuotaConfiguration'            
}            
Get-WmiObject -Computer SCCM2007 @WMIHT


Source: http://blogs.technet.com/b/askperf/archive/2008/09/16/memory-and-handle-quotas-in-the-wmi-provider-service.aspx

I’d recommend to stick to the piece of advice “do not modify these quotas for the sake of modifying them!” in the above link. In case you do, you can this way:

$WMIProviderConfig = Get-WmiObject @WMIHT            
$WMIProviderConfig.MemoryPerHost = 1024MB            
try {            
  $WMIProviderConfig.Put() | Out-Null            
  Write-Verbose -Message "Successfully changed the WMI provider settings" -Verbose            
} catch {            
  Write-Warning "Failed to modify the WMI provider because $($_.Exception.Message)"            
}

Note that a reboot is required.

Changing the ConfigMgr client health evaluator task (CCMEval)

My ConfigMgr 2012 book for training 10747A: Administering System Center 2012 Configuration Manager says on page 3-53 that:

This task runs ccmeval.exe at a time between 12:00AM and 1:00AM.

Ok, it’s fine to set a random time but not between midnight and 1:00AM…to avoid all computers sending info the Management Point at the same time.
Ok, the task is configured to run ASAP whenever a schedule is missed… for computers shut down at midnight and booting up in the morning.

If you’d like to introduce more randomness for this task, you can achieve it like this:

#Requires -Version 2

Function Set-CMCCMevalTaskTrigger {
<#
.SYNOPSIS
    Change the execution time of the CCMEval task

.DESCRIPTION
    Change the execution time of the CCMEval task by either setting a random start hour or a specified one

.PARAMETER ComputerName
    Array of string that represents the remote computers to target

.PARAMETER Hour
    Integer that sets the hour when the task will execute

.PARAMETER Random
    Switch to turn of a randomly chosen hour

.EXAMPLE
    Set-CMCCMevalTaskTrigger -Verbose
    Change to execution time on the local computer to 12:00AM instead of 12:00PM

.EXAMPLE
    "RemotePC1","RemotePC2" | Set-CMCCMevalTaskTrigger -Hour 13 -Verbose
    Change to execution time on the specified computers to 01:00PM

.EXAMPLE
    "RemotePC1","RemotePC2" | Set-CMCCMevalTaskTrigger -Random -Verbose
    Change to execution time on the specified computers to a random start time (hour)

#>
[CmdletBinding()]
Param(
[Parameter(ValueFromPipeline=$true,ValueFromPipeLineByPropertyName=$true)]
[Alias('CN','__Server','IPAddress','Server','hostname')]
[string[]]$ComputerName=$env:COMPUTERNAME,

[Parameter()]
[Alias('StartTime','Time')]
[ValidateRange(0,23)]
[int]$Hour = 12,

[Parameter()]
[switch]$Random

)
Begin{
    # Make sure we run as admin...
    $usercontext = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
    # ...in international mode
    $IsAdmin = $usercontext.IsInRole(544)
    if (-not($IsAdmin)) {
        Write-Warning "Must run powerShell as Administrator to perform these WMI queries"
        break
    }
   
    $TASK_UPDATE_FLAG = 0x4

    if ($PSBoundParameters.ContainsKey('Random')) {
        $Hour = (Get-Random -Maximum 23 -Minimum 0)
        $NewTime = 'T{0:00}:' -f $Hour
    } else {
        $NewTime = 'T{0:00}:' -f $Hour
    }
}
Process {
    $ComputerName | ForEach-Object -Begin {
        $TaskService = New-Object -com schedule.service
    } -Process {
        $Computer = $_
        try {
            # Connect to target computer
            $TaskService.Connect($Computer)
            # Get the XML definition of the task
            $taskDef = $TaskService.GetFolder('\Microsoft\Configuration Manager').GetTask('Configuration Manager Health Evaluation').Definition
            # Loop through Calendar triggers to change the start time
            $taskDef.Triggers | ForEach-Object -Process {
                if ($_.StartBoundary) {
                    $_.StartBoundary = ($_.StartBoundary -replace "T\d{2}:",$NewTime)
                }
            }
            # Update the existing task
            $TaskService.GetFolder('\Microsoft\Configuration Manager').RegisterTaskDefinition(
              'Configuration Manager Health Evaluation',
              $taskDef,
              $TASK_UPDATE_FLAG,
              $null,
              $null,
              $taskDef.Principal.LogonType
            )
            Write-Verbose -Message "Successfully set CCMeval taks execution time to $Hour on $Computer"
        } catch {
            Write-Warning -Message "Failed to change CCMeval task for computer $Computer because $($_.Exception.Message)"
        }
    }
}
End {}
}

System Center Configuration Manager Remote Control ACL issue

  • Context

I’m currently deploying the System Center Configuration Manager Agent 2012 SP1 over workstations that had previously the SCCM 2007 Client version 4.00.6487.2000

  • Symptoms

Helpdesk operators say that the remote control tool launches but that the client doesn’t see the window asking him to authorize or deny access to the helpdesk operator.

It looks like that all the process involved in the remote control are correctly launched on the client

gwmi win32_process | ? { $_.Commandline -match "CCM" } |  fl Commandline

The remote control window of the helpdesk operator doesn’t time out.

  • Analysis

Neither restarting the ConfigMgr services nor running the CCMEval task (Configuration Manager Health Evaluation) fixes the issue.
It’s time to digg in the numerous logs files of the ConfigMgr client:

Get-ChildItem C:\Windows\CCM\ -Include *.log -Recurse | Select-String -Pattern "SOFTWARE\\Microsoft\\SMS"

Using CMTrace.exe to read the logs, I can see that:
The log file under C:\windows\ccm\Logs\CmRcService.log contains the following errors:
Failed to open registry key Software\Microsoft\SMS\Client\Client Components\Remote Control\SessionStatus (0x80070005) for reading

Failed to open registry key ‘Software\Microsoft\SMS\Client\Client Components\Remote Control’. Permissions on the requested may be configured incorrectly.
Access is denied. (Error: 80070005; Source: Windows)

The log file under C:\windows\ccm\Logs\CcmExec.log contains the following additionnal info related to the issue
Failed to query size of Security (may not exist) (0x80070002)
Failed to get SID for ConfigMgr Remote Control Users (0x80070534)

  • Cause

If I compare a workstation that doesn’t have the issue and one that has, I can see that the registry permissions on HKLM\Software\SMS are not inherited from the parent. The BUILTIN\Users ACL is actually missing which causes the issue.

Here’s what I can see with PowerShell on workstation that has its registry permissions not set correctly:

$acl = (Get-ACL HKLM:\SOFTWARE\Microsoft\SMS)            
$acl.Access            
$acl.Sddl



The corresponding SDDL looks like: O:BAG:SYD:P(A;OICI;KA;;;SY)(A;OICI;KA;;;BA)

  • Solution

Although I don’t have time to fill-in a bug or to understand the root cause, I came up with the following quick fix.

if (Test-Path HKLM:\SOFTWARE\Microsoft\SMS) {            
    $acl = (Get-ACL HKLM:\SOFTWARE\Microsoft\SMS)            
            
    # SetAccessRuleProtection(bool isProtected, bool preserveInheritance)            
    # Allow inheritance            
    $acl.SetAccessRuleProtection($false,$false)            
                    
    # Enumerate all Access rules that are not inherited            
    # GetAccessRules(bool includeExplicit, bool includeInherited, type targetType)}            
    $acl.GetAccessRules($true,$false,[System.Security.Principal.NTAccount]) | ForEach-Object -Process {            
        # RemoveAccessRule(System.Security.AccessControl.RegistryAccessRule rule)            
        $acl.RemoveAccessRule($_) | Out-Null            
    }            
    # Set the ACL            
    try {            
        Set-Acl HKLM:\SOFTWARE\Microsoft\SMS -AclObject $acl -ErrorAction Stop            
    } catch {            
        Write-Warning -Message "Failed to restore inherited ACL from parent because $($_.Exception.Message)"            
    }                    
}

Note that the 2nd parameter of the .Net SetAccessRuleProtection Method (preserveInheritance), is ignored if the first (isProtected) is set to false. More on the following MSDN page.

As I have enabled the Powershell remoting I can also execute on a remote computer by simply passing the above code as a scriptblock to the Invoke-Command cmdlet.
Did I already say that Powershell rocks! 😎