Welcome to MrBoDean.net on Github pages

Distribution Points not reporting Usage data

2017-02-15

I am scratching my head about the what happened on this issue, but let me explain. We had a issue come up in our SCCM RAP. (If you are a Premier Customer, I highly recommend the RAP as a service. Get it, Use it, and Use it often.) There were about 30ish distribution points that were not reporting usage stats for our 2012 R2 Configuration Manager site. A quick check showed that the distribution points where alive and well but that the scheduled task that reports the usage statistics was gone. If you need a good primer on how to check out a distribution point see this post from Scott’s IT Blog. To resolve this I simply exported the task from a working distribution point and imported on the systems where it was missing. To be honest though it did not get fixed everywhere. A couple of months go by and we rerun the RAP and look at the results. Now there are over 300 distribution points not reporting statistics because of a missing scheduled task. This makes me beg and plead to speed up our upgrade project for Current Branch. But until then I have to keep everything going, so a little powershell to the rescue.

First I choose to export the existing scheduled task from a working server and save it as c:\temp\Content Usage.xml

The Rap web site is great for reporting the issue but not so much for getting the details in a way that is easily usable in a script. So here is a SQL query to identify distribution points not reporting usage date.

Select  DP.ServerName
From v_DistributionPoints AS DP
Where Not Exists 
    (Select DPStats.PkgServer from v_DPUsageSummary As DPStats
     Where DPStats.PkgServer = DP.ServerName)

This will give you a list of server names that you can save in a file. Now for the powershell to recreate the scheduled task and run it.

$servers = get-content c:\temp\Servers.txt
foreach($server in $servers){
  $server
  schtasks /create /xml "c:\temp\Content Usage.xml" /TN "Microsoft\Configuration Manager\Content Usage" /s $server
  schtasks /run /TN "Microsoft\Configuration Manager\Content Usage" /s $server
}

Give it a little time and rerun the SQL query to verify that the systems are reporting usage data and are being removed from the report.

Powershell Saturday - Nashville 

2017-02-06

MVP Mick Pletcher is organizing a PowerShell Saturday event for Nashville. It is still in the planning stages but I will be there. Once the details are finalized I will share them. Hopefully if you are in the area you can join in on the fun. These events are a great way to learn and interact will MVPs and others passionate about using PowerShell. The planned date is for Saturday June 24, 2017. I will be speaking on Powershell for SCCM.

Updating SCCM Client Logging Options

2017-02-05

If you spend anytime supporting System Center Configuration Manager, you will develop a special love for log files. Often I find that I need to change the logging options on a small group of clients to troubleshoot. It could be because I need a larger file size or need to enable debug logging. While making the changes is fairly painless on one system, doing it on several can drive me to drink several cups of coffee. (To be fair just going to work drives me to drink….coffee.) Here is the script I use to make the changes and then put everything back to normal afterwards. The script is below but I am including the Powershell Gallery and GitHub Links as well.

Powershell Gallery https://www.powershellgallery.com/packages/Set-CMClientLogOptions/1.0.0/DisplayScript

GitHub https://github.com/mrbodean/Technet/blob/master/Powershell/Set-CMClientLogOptions/Set-CMClientLogOptions.ps1

<#PSScriptInfo
.Description
    Sets the Configuration Manager Client Logging Options
.VERSION 
    1.0.0
.GUID 
    56dfd8ea-5998-4524-9264-edfa65b4cc96
.AUTHOR 
    Jonathan Warnken - @MrBodean - http://www.mrbodean.net/
.COMPANYNAME 

.COPYRIGHT 
    (C) Jonathan Warnken 2017 All rights reserved.
.TAGS 
    SCCM, ConfigMan, Configuration Manager, Client 

.LICENSEURI
    https://github.com/mrbodean/Technet/blob/master/Powershell/Set-CMClientLogOptions/License
.PROJECTURI 
    https://github.com/mrbodean/Technet/tree/master/Powershell/Set-CMClientLogOptions
.ICONURI 

.EXTERNALMODULEDEPENDENCIES 

.REQUIREDSCRIPTS 

.EXTERNALSCRIPTDEPENDENCIES 

.RELEASENOTES
    Initial Release

#>

<#
.Synopsis
   Set the Log Level for the Configuration Manager Client 
.EXAMPLE
   Set-CMClientLogLevel -LogLevel Normal -Computername SomeComputer1
.EXAMPLE
   $parms @{
    LogLevel = "Debug"
    LogMaxHistory = 3
    LogMaxSize = 500000
    ComputerName = "SomeComputer1", "SomeComputer2","SomeComputer3"
   }
   Set-CMClientLogLevel @parms
.NOTES
    Author
        Jon Warnken
        @MrBoDean
        jon.warnken@gmail.com
#>
param(
    # Level of Logging to set 
    [Parameter(ValueFromPipelineByPropertyName=$true)]
    [ValidateSet("Debug","Normal","Off")]
    [string]$LogLevel,
    # Computer name(s) to set the logging on 
    [Parameter(Mandatory=$true,
                ValueFromPipelineByPropertyName=$true)]
    [String[]]$Computername,
    # Number of log files to keep
    [Parameter(ValueFromPipelineByPropertyName=$true)]
    [int]$LogMaxHistory,
    # Max size of log file
    [Parameter(ValueFromPipelineByPropertyName=$true)]
    [int]$LogMaxSize
)

<#
.Synopsis
   Set the Log Level for the Configuration Manager Client 
.EXAMPLE
   Set-CMClientLogLevel -LogLevel Normal -Computername SomeComputer1
.EXAMPLE
   $parms @{
    LogLevel = "Debug"
    LogMaxHistory = 3
    LogMaxSize = 500000
    ComputerName = "SomeComputer1", "SomeComputer2","SomeComputer3"
   }
   Set-CMClientLogLevel @parms
#>
function Set-CMClientLogOptions
{
    [CmdletBinding()]
    [OutputType([string])]
    Param
    (
        # Level of Logging to set. Must be Debug, Normal, or Off
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [ValidateSet("Debug","Normal","Off")]
        [string]$LogLevel,
        # Computer name(s) to set the logging on 
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [String[]]$Computername,
        # Number of log files to keep
        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [int]$LogMaxHistory,
        # Max size of log file
        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [int]$LogMaxSize
    )

    Begin{
        Switch($Loglevel){
            "Debug"{$logging = 0}
            "Normal"{$logging = 1}
            "Off"{$logging = 2}
        }
        $action =  {
            $loglevelvalue = $args[0]
            $maxhistoryvalue = $args[1]
            $maxsizevalue = $args[2]
            $regpath = "Registry::HKLM\SOFTWARE\Microsoft\CCM\Logging\@GLOBAL"
            $DebugLoggingPath = "Registry::HKLM\SOFTWARE\Microsoft\CCM\Logging\DebugLogging"
            $loglevelname = "LogLevel"
            $maxhistname = "LogMaxHistory"
            $maxsizename = "LogMaxSize"
            $update = $false
            $CurentloglevelValue = Get-ItemPropertyValue -Path $regpath -Name $loglevelname
            $CurrentmaxhistValue = Get-ItemPropertyValue -Path $regpath -Name $maxhistname
            $CurrentmaxsizeValue = Get-ItemPropertyValue -Path $regpath -Name $maxsizename
            If(loglevelvalue){
                if($CurentloglevelValue -eq $loglevelvalue){
                    Write-Output "Current Log Level matched requested value. No action taken."
                }else{
                    Set-ItemProperty -Path $regpath -name $loglevelname -value $loglevelvalue
                    Write-Output "Successfully set the Log Level"
                    $update = $true
                }
                Switch($loglevelvalue){
                    0{
                        if(Get-Item $DebugLoggingPath -ErrorAction SilentlyContinue){
                            Write-Output "DebugLogging Key found. No action taken."
                        }else{
                            New-Item -Path $DebugLoggingPath
                        }
                    }
                    Default{
                        if(Get-Item $DebugLoggingPath -ErrorAction SilentlyContinue){
                            Remove-Item -Path $DebugLoggingPath -Force 
                        }else{
                            Write-Output "DebugLogging Key Not found. No action taken."
                        }
                    }
                }
            }          
            if($maxhistoryvalue){
                if($CurrentmaxhistValue -eq $maxhistoryvalue){
                     Write-Output "Current Log Max matched requested value. No action taken."
                }else{
                    Set-ItemProperty -Path $regpath -name $maxhistname -value $maxhistoryvalue
                    Write-Output "Successfully set the Log Max"
                    $update = $true
                }
            }
            if($maxsizevalue){
                if($CurrentmaxsizeValue -eq $maxsizevalue){
                     Write-Output "Current Log Max matched requested value. No action taken."
                }else{
                    Set-ItemProperty -Path $regpath -name $maxsizename -value $maxsizevalue
                    Write-Output "Successfully set the Log Max Size"
                    $update = $true
                }
            }
            if($update){
                Write-Output "Update were made to the client config. The ccmexec service will be recycled to use the updates."
                Stop-Service -Name CcmExec -Force
                Start-Service -Name CcmExec
            }
        }
    }
    Process{
        Foreach($computer in $Computername){
            Invoke-Command -ComputerName $computer -ScriptBlock $action -ArgumentList $logging,$LogMaxHistory,$LogMaxSize
        }
    }
}
Set-CMClientLogOptions @PSBoundParameters

Reporting on the Total Physical Memory installed vs the Memory visible to the OS

2017-01-19

Recently I was asked if SCCM could report on the total physical memory installed. No problem there is even a built in report for that, I replied. No the requester explained, on systems with a 32 OS installed those reports only show the max memory the OS can see. So we sit down and go through the Resource Explorer and find that we are collecting the Win32_PhysicalMemory Class from WMI as part of our hardware inventory. A quick little query gets the info for that can be made into a report.

Select RSystem.Name0 As Name, RSystem.Netbios_Name0 As 'NetBios Name', 
SUM(PhyMem.Capacity0) As 'Total Physical Memory Installed (MB)',
OS.TotalVisibleMemorySize0 As 'OS Total Memory(MB)', 
GSystem.SystemType0 As 'System Type',
OS.Caption0 As OS, Processor.Name0 As Processor,PC.Model0 As Model
From v_R_System As RSystem
Inner Join v_GS_PHYSICAL_MEMORY As PhyMem on PhyMem.ResourceID = RSystem.ResourceID
Inner Join v_GS_OPERATING_SYSTEM As OS on OS.ResourceID = RSystem.ResourceID
Inner Join v_GS_SYSTEM As GSystem on GSystem.ResourceID = RSystem.ResourceID
Inner Join v_GS_PROCESSOR As Processor On Processor.ResourceID = RSystem.ResourceID
Inner Join v_GS_COMPUTER_SYSTEM As PC On PC.ResourceID = RSystem.ResourceID
Group By RSystem.Name0, RSystem.Netbios_Name0, OS.TotalVisibleMemorySize0,
GSystem.SystemType0,OS.Caption0, Processor.Name0,PC.Model0
Order By RSystem.Name0

Why you should not like "like"

2016-10-06

So for the past two days I have been checking and triple checking the Configuration Manager environment I support at work. Nothing like a hurricane being labeled “worst case” storm to make you shake the dust of the DR plans. After all the backup checks and distribution point health and content validations, I started looking and the performance of various components. Overall nothing major found but while checking the collection evaluations I did find a few collections that stood out for poor performance. Only one was real nasty and over 2 minutes. The collection is not very large in terms of members but the query populating it needed a little work. So before I dig into the details, you may like to know how to identify the issue. You can find all the info you need in the “colleval.log”. If you use a little googlefu there are some good tips on how to parse the log with powershell and identify your troublemakers. Or you can use the Collection Evaluation Viewer from the System Center 2012 R2 Toolkit. If you have never used it before The Config Ninja has a great post walking you through it and some reports to display the same info.

With the Collection Evaluation Viewer you can use the run time to identify collections that need some review. When you identify a collection to review open the properties and look at the membership rules. Here is an example of a collection query that was running longer then it should.

select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,SMS_R_SYSTEM.ResourceDomainORWorkgroup,SMS_R_SYSTEM.Client 
from SMS_R_System inner join SMS_G_System_SoftwareProduct on SMS_G_System_SoftwareProduct.ResourceID = SMS_R_System.ResourceId 
where SMS_R_System.ADSiteName = "SOMEADSITE" And SMS_G_System_SoftwareProduct.ProductName like "%SAP GUI for Windows%"

In some environments this may complete in just a few seconds but it was taking over 2 minutes for me. On the whole this is a fairly normal requirement for a deployment. All computers in a location with Software X installed. But the database has to get all of the system records in the location and then check all of the product names it has reported and check to see if the name is “Like” the value in the query. Now there is nothing wrong with Like and there are lots of cases where you must use it. But you have to understand that it is a more expensive cost to SQL queries using them. So to check out what the possible returns were and what the wild cards were allowing the query to collect I queried the view in sql.

select v_GS_SoftwareProduct.ProductName 
from v_GS_SoftwareProduct
where v_GS_SoftwareProduct.ProductName Like '%SAP GUI for Windows%'
Group BY v_GS_SoftwareProduct.ProductName

And the query returned a single product name of SAP GUI for Windows

So to solve this query’s performance issue, I just switched to = and the collection evaluation run time went down to 3.5 seconds

select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,SMS_R_SYSTEM.ResourceDomainORWorkgroup,SMS_R_SYSTEM.Client from SMS_R_System inner join SMS_G_System_SoftwareProduct on SMS_G_System_SoftwareProduct.ResourceID = SMS_R_System.ResourceId where SMS_R_System.ADSiteName = "SOMEADSITE" and SMS_G_System_SoftwareProduct.ProductName = "SAP GUI for Windows"

Now that the longest running evaluation was resolved there where a couple of collections that where taking 15 - 20 seconds to complete. Not terrible but not good either, as I looked through them I found a couple of things to share. First up is another collection for computers with a specific type of software installed.

select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,SMS_R_SYSTEM.ResourceDomainORWorkgroup,SMS_R_SYSTEM.Client 
from SMS_R_System inner join SMS_G_System_ADD_REMOVE_PROGRAMS on SMS_G_System_ADD_REMOVE_PROGRAMS.ResourceID = SMS_R_System.ResourceId 
where SMS_G_System_ADD_REMOVE_PROGRAMS.DisplayName LIKE "ODBC Driver for Teradata 14"

So I go to SQL and check how many Display names are returned by the wild card query and get back two. So this time changing to a “in list” query reduced collection evaluation time.

select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,SMS_R_SYSTEM.ResourceDomainORWorkgroup,SMS_R_SYSTEM.Client 
from SMS_R_System inner join SMS_G_System_ADD_REMOVE_PROGRAMS on SMS_G_System_ADD_REMOVE_PROGRAMS.ResourceID = SMS_R_System.ResourceId 
where SMS_G_System_ADD_REMOVE_PROGRAMS.DisplayName in ("ODBC Driver for Teradata 14.10","ODBC Driver for Teradata 14.10.0.2")

One thing to make note of is you need to consider is if the values being returned are going to change often and will you know about the changes. But in general I would use the original query with an ad-hoc query or a report. The explicit values for the collection membership query are appropriate because of the impact to the collection evaluation process.

For the last example I am going to use a query that need to use like. This query is evaluating the computer name and the author needed to include systems with a specific range of ending values and a specific character starting the computer name. Along with a few exclusions.

select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,SMS_R_SYSTEM.ResourceDomainORWorkgroup,SMS_R_SYSTEM.Client 
from SMS_R_System   
where (SMS_R_System.Name like "X%%%%A101" or SMS_R_System.Name like "X%%%%A102" or
SMS_R_System.Name like "X%%%%A103" or SMS_R_System.Name like "X%%%%A104" or 
SMS_R_System.Name like "X%%%%A105" or SMS_R_System.Name like "X%%%%A106" or 
SMS_R_System.Name like "X%%%%A107" or SMS_R_System.Name like "X%%%%A108" or 
SMS_R_System.Name like "X%%%%A109" or SMS_R_System.Name like "X%%%%A110" or 
SMS_R_System.Name like "X%%%%A111" or SMS_R_System.Name like "X%%%%A112" or 
SMS_R_System.Name like "X%%%%A113" or SMS_R_System.Name like "X%%%%A114" or 
SMS_R_System.Name like "X%%%%A115" or SMS_R_System.Name like "X%%%%A116" or 
SMS_R_System.Name like "X%%%%A117" or SMS_R_System.Name like "X%%%%A118" or 
SMS_R_System.Name like "X%%%%A119" or SMS_R_System.Name like "X%%%%A120" or 
SMS_R_System.Name like "X%%%%A121" or SMS_R_System.Name like "X%%%%A122" or 
SMS_R_System.Name like "X%%%%A123" or SMS_R_System.Name like "X%%%%A124" or 
SMS_R_System.Name like "X%%%%A125" or SMS_R_System.Name like "X%%%%A126" or 
SMS_R_System.Name like "X%%%%A127" or SMS_R_System.Name like "X%%%%A128" or 
SMS_R_System.Name like "X%%%%A129" or SMS_R_System.Name like "X%%%%A130" or 
SMS_R_System.Name like "X%%%%A131" or SMS_R_System.Name like "X%%%%A132" or 
SMS_R_System.Name like "X%%%%A133" or SMS_R_System.Name like "X%%%%A134" or 
SMS_R_System.Name like "X%%%%A135" or SMS_R_System.Name like "X%%%%A136" or 
SMS_R_System.Name like "X%%%%A137" or SMS_R_System.Name like "X%%%%A138" or 
SMS_R_System.Name like "X%%%%A139" or SMS_R_System.Name like "X%%%%A140" or 
SMS_R_System.Name like "X%%%%A141" or SMS_R_System.Name like "X%%%%A142" or 
SMS_R_System.Name like "X%%%%A143" or SMS_R_System.Name like "X%%%%A144" or 
SMS_R_System.Name like "X%%%%A145" or SMS_R_System.Name like "X%%%%A146" or 
SMS_R_System.Name like "X%%%%A147" or SMS_R_System.Name like "X%%%%A148" or 
SMS_R_System.Name like "X%%%%A149" or SMS_R_System.Name like "X%%%%A150" or 
SMS_R_System.Name like "X%%%%A151" or SMS_R_System.Name like "X%%%%A152" or 
SMS_R_System.Name like "X%%%%A153" or SMS_R_System.Name like "X%%%%A154" or 
SMS_R_System.Name like "X%%%%A155" or SMS_R_System.Name like "X%%%%A156" or 
SMS_R_System.Name like "X%%%%A157" or SMS_R_System.Name like "X%%%%A158" or 
SMS_R_System.Name like "X%%%%A159" or SMS_R_System.Name like "X%%%%A160" or 
SMS_R_System.Name like "X%%%%A161" or SMS_R_System.Name like "X%%%%A162" or 
SMS_R_System.Name like "X%%%%A163" or SMS_R_System.Name like "X%%%%A164" or 
SMS_R_System.Name like "X%%%%A165" or SMS_R_System.Name like "X%%%%A166" or 
SMS_R_System.Name like "X%%%%A167" or SMS_R_System.Name like "X%%%%A168" or 
SMS_R_System.Name like "X%%%%A169" or SMS_R_System.Name like "X%%%%A170" or 
SMS_R_System.Name like "X%%%%A171" or SMS_R_System.Name like "X%%%%A172" or 
SMS_R_System.Name like "X%%%%A173" or SMS_R_System.Name like "X%%%%A174" or 
SMS_R_System.Name like "X%%%%A175" or SMS_R_System.Name like "X%%%%A176" or 
SMS_R_System.Name like "X%%%%A177" or SMS_R_System.Name like "X%%%%A178" or 
SMS_R_System.Name like "X%%%%A179" or SMS_R_System.Name like "X%%%%A180" or 
SMS_R_System.Name like "X%%%%A181" or SMS_R_System.Name like "X%%%%A182" or 
SMS_R_System.Name like "X%%%%A183" or SMS_R_System.Name like "X%%%%A184" or 
SMS_R_System.Name like "X%%%%A185" or SMS_R_System.Name like "X%%%%A186" or 
SMS_R_System.Name like "X%%%%A187" or SMS_R_System.Name like "X%%%%A188" or 
SMS_R_System.Name like "X%%%%A189" or SMS_R_System.Name like "X%%%%3190" or 
SMS_R_System.Name like "X%%%%3191" or SMS_R_System.Name like "X%%%%3192" or 
SMS_R_System.Name like "X%%%%3193" or SMS_R_System.Name like "X%%%%3194" or 
SMS_R_System.Name like "X%%%%3195" or SMS_R_System.Name like "X%%%%3196" or 
SMS_R_System.Name like "X%%%%3197" or SMS_R_System.Name like "X%%%%3198" or 
SMS_R_System.Name like "X%%%%3199") AND SMS_R_System.Name not like "XB9%" AND 
SMS_R_System.Name not like "XC9%"

Right away we know the original author did not understand WQL operators. By using the correct operators when you must use like, the query is simplified and performs much better.

select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,SMS_R_SYSTEM.ResourceDomainORWorkgroup,SMS_R_SYSTEM.Client 
from SMS_R_System 
where (SMS_R_System.Name like "X____A1[0-9][0-9]" 
or SMS_R_System.Name like "X____319[0-9]") 
AND SMS_R_System.Name not like "XB9%" 
AND SMS_R_System.Name not like "XC9%" 
AND SMS_R_System.Name not like "X____A100"

Evaluating for single characters with an underscore “_” is quicker then using the percent “%” for any and all character combinations. If you need to query for a specific number of any characters use multiple underscores. Specifying the range allows the query to be much shorter and simpler. 7