Visual Studio 2017 Editions using ConfigMgr Configuration item

This is a companion to It *might* be a replacement for the previous mof edit; but I haven't tested this enough to make that conclusion--test yourself to see.

Issue to be resolved:  there are licensing groups at my company who are tasked with ensuring licensing compliance.  There is a significant difference between Visual Studio costs for Standard, Professional, and Enterprise.  Prior to Visual Studio 2017, that information was able to be obtained via registry keys, and a configuration.mof + import (see link above) was sufficient to obtain that information.

According to (looks like published date is June, 2017), that information is no longer in the registry.  There is a uservoice published --> <--, requesting that the devs for visual studio put that back--but there's no acknowledgement that it would ever happen.

So that means that us lonely SCCM Administrators, tasked with "somehow" getting the edition information to the licensing teams at our companies have to--yet again--find a way to "make it happen", using the tools provided.  So here's "one possible way". 

This has only been tested on ONE device in a lab... so it's probably not perfect.  Supposedly, using the -legacy switch it'll also detect "old versions" installed--but I have no idea if that works or not.  Might not.

Here's how I plan on deploying this...

1)  configuration Item, Application Type.
    a) 'Detection Method", use a powershell script... this may not be universal, but currently in my lab, this location of 'vswhere.exe' is consistently in the same place.  Here's hoping it'll not change.  So the detection logic for the CI to bother to run at all would be "do you have vswhere.exe where I think it should be":

 $ErrorActionPreference = 'SilentlyContinue'
 $location = ${env:ProgramFiles(x86)}+'\Microsoft Visual Studio\Installer\vswhere.exe'
 if ([System.IO.File]::Exists($location)) {
  write-host $location

    b) Setting, Discovery Script, see the --> attached <-- .ps1 file.  Compliance Rule would be just existential, any result at all.
2)  Deploy that CI in a Baseline, as 'optional'; whether or not I just send it to every box everywhere, or create a collection of machines with Visual Studio 2017 in installed software--either way should work.
3)  Once Deployed and a box with Visual Studio 2017 has run it, confirm that a sample box DOES create a root\cimv2, cm_vswhere class, and there is data inside.
4)  Enable inventory
    a) In my SCCM Console, Administration, Client Settings, right-click Default Client Settings, properties
    b) Hardware Inventory, Set Classes...
    c) Add...
    d) Connect... to "the computer you checked in step 3 above; where you confirmed there is data locally on that box in root \cimv2, cm_vswhere"  and root\cimv2
    e) find the class "cm_vswhere"  check the box, OK. OK. OK.
5) monitor
    a) on your primary site, <installed location for SCCM>\Logs, dataldr.log 
    b) It'll chat about pending adds in the log.  Once that's done, you'll see a note about how it made some views for you.  "Creating view for..."
6) Wait a day, and then look if there is any information in a view probably called something like... v_gs_cm_vswhere.  But your view might have a different name--you'll just have to look.
    a) if you're impatient, back on that box from step 3 above, do some policy refreshes.  then a hardware inventory.
5) End result, you should get information in the field "displayName0", like "Visual Studio Professional 2017", and you'll be able to make custom reports using that information.  Which should hopefully satisfy your licensing folks.

To reiterate... tested on ONE box in a lab.  Your mileage my vary.  Additional tweaks or customizations may be needed to the script.  That's why in the script I tried to add a bunch of 'write-verbose'.  If you need to figure out why something isn't working right, change the VerbosePreference to Continue, not SilentlyContinue, and run it interactively on a machine--to hopefully figure out and address any un-anticipated flaws.

SCCM, ConfigMgr

  • Created on .

Configuration Manager Current Branch FastChannel Information via SQL Query

A lot of people use the console--but I don't go in there that much.  I'm more of a query SQL kind of person.  Some of the updates lately for Current Branch have been leveraging the "FastChannel" for communications.  If you don't remember, originally the FastChannel was meant for quick-hit communications, primarily around Endpoint protection.  However, over the last several updates, the product team has been adding more communications over the fast channel.  Most of those communications are to make the console experience feel more "real time"--and I get that.  For people who live in the console.  but I don't... so where is that information and how can I use it... using SQL?

Here's a couple things to have in your SQL query backpocket.

If you are Current Branch 1710 or higher, the 1710 clients will communicate back about if they have 1 or more of 4 specific "reboot pending" reasons.  You can see that in console--but as a SQL report, here's a summary query to show you counts of devices and what reboot pending state (and why) they are in:

select cdr.ClientState [Pending Reboot],
Case when (1 & cdr.ClientState) = 1 then 1 else 0 end as [Reason: ConfigMgr],
Case when (2 & cdr.ClientState) = 2 then 1 else 0 end as [Reason: Pending File Rename],
Case when (4 & cdr.ClientState) = 4 then 1 else 0 end as [Reason: Windows Update],
Case when (8 & cdr.ClientState) = 8 then 1 else 0 end as [Reason: Windows Feature],
Count(*) [Count]
from vSMS_CombinedDeviceResources cdr
where CAST(right(left(cdr.ClientVersion,9),4) as INT) >= 8577 and cdr.clientversion > '1'
Group by cdr.ClientState
order by cdr.clientstate

It'll only tell you about clients which are version 8577 or higher (aka, 1710).  If you are absolutely certain all your clients are 1710 or higher, you can remove that section of the "where" clause.
asking for clientversion > 1 is because you "might" have mobile clients reporting to your CM.  You really only want to know about Windows-based clients.  Essentially, those where clauses are so that you can be a little more accurate about pending reboots.  If you have a lot of clients less than version 1710, they can't communicate their clientState via the FastChannel, so you might think "great, these devices don't have a pending reboot"--when what it really means is "these clients aren't able to tell me if they need a pending reboot, because their client version is not capable of telling me that, via this method".

Another piece of information that can come in via the Fast Channel, if you are using Current Branch 1806 or higher, 1806 clients can tell you about a CURRENTLY logged in user.  This differs from what we as SMS/ConfigMgr admins are used to in the past.  We have for years been able to tell "last logged on user" or "most likely primary user"--based on heartbeat, hardware inventory, or asset intelligence data.  But that could be "old news"--depending upon how frequent your heartbeat or inventory runs, it could be hours to days old information.  Current logged on user should be at worst a few minutes old (depending of course upon your size, and complexity)

select s1.netbios_name0 [ComputerName], cdr.CurrentLogonUser [Current Logged on User According to FastChannel]
from vSMS_CombinedDeviceResources cdr
join v_r_system s1 on s1.resourceid=cdr.machineid
oder by s1.netbios_name0


  • Created on .

Configuration Manager Collection Cleanup Suggestions

Certainly in your CurrentBranch Console, under "Management Insights", there are some things there regarding collection cleanup; but here's a few other ways to look at your data.

Over the years, Collection plaque and tartar just grows and grows... and over time, people forget what collections were made for, or why they have them.  As a way to help the people who use our console narrow it down a bit to 'possible' stale, old collections which no longer have any purpose, below is a potential starting point.

What the below would list is collectionids and names, which are:
- NOT Currently used for any other collection as a "limited to", "Include", or "Exclude"
- NOT Currently used for any Deployment, whether it's a baseline, an application, an advertisement, or a task sequence
- NOT Currently used to define a Service Window (aka Maintenance Window)
- NOT Currently used for any custom client agent settings you might have configured.
- NOT currently used for any collection variables you might have for OSD
- NOT currently used for Automatic Client Upgrade, as an excluded collection
- NOT a default/out of the box collection (aka, ones that start with SMS)

This isn't of course a definitive list.  For example, perhaps a collection was created to deploy "Really Important Application" 2 weeks ago... but the actual deployment hasn't happened yet--it's destined to begin next week.  In that case of course the collection might show up on this list--but it shouldn't be deleted--it has a future use.  But hopefully if your environment has a lot of collections and determining which ones might be safe to remove, this is a potential starting point.

Select c.collectionid, [CollectionName]
from v_collection c
    c.collectionid not in (Select SourceCollectionID from vSMS_CollectionDependencies) -- include, excludes, or limited to
and c.collectionid not in (Select collectionid from v_deploymentsummary) -- any deployment, apps, advert, baseline, ts
and c.collectionid not in (Select Collectionid from v_ServiceWindow)
and c.collectionid not in (select collectionid from vClientSettingsAssignments)
and c.collectionid not in (select siteid from vSMS_CollectionVariable) -- OSD Collection Variables
and c.collectionid not in (Select a.ExcludedCollectionID from autoClientUpgradeConfigs a) -- ACU exclusion collection
and c.collectionid not in (select collectionid from v_collection where collectionid like 'sms%') -- exclude default collections

Another potential sql query for you to look for "collections not needed" could be this one.  What this one would be is it would sort, by "last time members changed in this collection".  The potential argument goes like this... even *if* that collection is being used for an active deployment... if the members of that machine based (not userbased) collection hasn't changed in years; how important is it to keep that particular deployment going / available?

;with cte as (select t2.CollectionName, t2.SiteID [CollectionID]
 ,(Cast(t1.EvaluationLength as float)/1000) as [EvalTime (seconds)]
 ,t1.LastRefreshTime, t1.MemberChanges, t1.LastMemberChangeTime,sc.SiteCode,
  when c.refreshtype = 1 then 'Manual'
  when c.refreshtype = 2 then 'Scheduled'
  when c.refreshtype = 4 then 'Incremental'
  when c.refreshtype = 6 then 'Scheduled and Incremental'
 end as [TypeofRefresh]
from dbo.collections_L t1 with (nolock)
join collections_g as t2 with (nolock) on t2.collectionid=t1.collectionid
join v_sc_SiteDefinition sc on sc.SiteNumber=t1.SiteNumber
join v_collection c on c.collectionid=t2.siteID
Select cte.collectionID, cte.CollectionName, CTE.[EvalTime (seconds)]
,Right(Convert(CHAR(8),DateADD(SECOND,CTE.[EvalTime (seconds)],0),108),5) [EvalTime (Minutes:Seconds)]
,cte.lastrefreshtime, cte.memberchanges, cte.lastmemberchangetime, cte.typeofrefresh, cte.membercount
from cte
where cte.collectiontype=2
and cte.collectionid not like 'SMS%'
order by lastmemberchangetime


  • Created on .

Reporting on PST Files for Outlook using SCCM

This is an update to this: .  The reason for the update is the old method (from 2013) worked for older versions of Outlook; but not for 2013 or newer.

With some clever scripting from different people, notably , John Marcum and Sherry Kissinger, and we've got a routine that will, for the most part, answer those three questions.  The basics of how it works is this.  There are two vbscripts that run.  One runs as SYSTEM, and it's only purpose is to create a custom namespace in WMI, and grant permissions to all of your domain users to that custom namespace--so they can populate it with the results of script #2.  Script #2 runs, only when a user is logged in, with user rights.  That's because the majority of what the script needs to do is read information about that specific logged-in users Outlook configuration, and (potentially) any mapped drive information which may be referenced by the PST file location.

The results of the 2nd script end up in that custom WMI namespace, and will have the following information:

DateScriptRan = the exact date and time that the script ran to gather this user-specific information.
FileSizeinMB = If it could be detected, and the file size was 1mb or larger, the size of the PST.  If it's less than 1mb, or for whatever reason could not be detected, the value will be 0.
PSTFile = The DisplayName in Outlook
PSTLocation = The location as known to Outlook
Type = If it could figure out that Q: was a mapped network drive, it'll say 'Remote', otherwise it'll say local
UserDomain = whomever is logged in, what their domain is.
UserName = whomever is logged in, what their username is.
Location = This will either be a drive letter, or if it was possible to determine if that drive letter was really a mapped drive to a network location, the \\Server\Share will be populated.

End result:  After deploying these two scripts, you will be able to answer those pesky questions from your Exchange team about who, where, and how large, are referenced PST files.  Of course, the main limitation is this is per-user information.  If you have a lot of shared machines, or the same user has multiple computers (and connects to the same PST files on those multiple computers) you'll have to do some creative reporting to ensure you don't double-count the same PST files.

Ok, enough of how it works.  You really want to know *exactly* what to do, right?  Let's start!
Your Source folder for the package will contain 2 things:

The .ps1 files are at this  -->link<--. 

You will need to make 1 change to "CreateCustomCMClasses-RunAsSystem.ps1", this line:
    [String]$Account   = 'YourDomainHere\Domain Users',
Modify that to be your domain (the domain your users are in that will be logging in and running script #2).

Create two programs; the first runs Powershell.exe -ExecutionPolicy Bypass -File CreateCustomCMClasses-RunAsSystem.ps1, whether or not a user is logged in, with Administrator rights.  The second runs Powershell.exe -ExecutionPolicy Bypass -File PopulateWMI-RunAsUser.ps1, only when a user is logged in, with user rights.  The 2nd one; you want to "run another program first", and have it run the first one.  It only needs to run the 1st program once, per computer; it doesn't need to re-run.

Advertise the 2nd program to a collection (I recommend a test/pilot first), and confirm that it works as you expect.  If you want to confirm the data is there, look in root\CustomCMClasses  (not root\cimv2) for cm_PSTFileInfo, that there are instances there for any Outlook-attached PST files for that user.

If you are satisfied it's there locally, import the below into Default Client Agent Settings, Hardware Inventory


class cm_pstfileinfo : SMS_Class_Template
  [SMS_Report(TRUE)] string DateScriptRan;
  [SMS_Report(TRUE)] uint32 FileSizeinMB;
  [SMS_Report(TRUE)] string Location;
  [SMS_Report(TRUE)] string PSTFile;
  [SMS_Report(TRUE),key] string PSTLocation;
  [SMS_Report(TRUE)] string Type;
  [SMS_Report(TRUE)] string UserDomain;
  [SMS_Report(TRUE)] string UserName;

sit back, relax for a bit... then invoke a hardware inventory on your test boxes, and see if the data shows up in your database in v_gs_pstfileinfo0.  If so, deploy the advert to your real target collection of users or computers, and wait for the data to show up.  Depending upon your need for this information; you may or may not want to have the advert run on a recurring basis (weekly? monthly?) or just gather it for a week or so (just enough to answer the question) then delete the advert and change the Inventory from TRUE to FALSE (until the next time they ask).

Here's a potential sql report to get you started:

select sys.Name0 as [Computer Name],
pst.UserName0 as [User],
pst.PSTFile0 as [File Name],
pst.PSTLocation0 as [File Location],
pst.Type0 as [Local/Remote],
pst.Location0 as [Location],
pst.FileSizeinMB0 as [Size in MB],
pst.DateScriptRan0 as [Date Collected]
from v_R_System sys
Inner Join v_GS_PSTFileInfo0 pst on sys.ResourceID = pst.ResourceID
order by sys.Name0

  • Created on .

ConfigMgr RefreshServerComplianceState as a Configuration Item

State messages are great, because they are quickly processed.  However, it can (and does) occasionally happen where for network reasons, corrupt data, or other influences, some State Messages from your ConfigMgr Clients never make it from the client into your database.  Normally, that isn't a big deal--however, what does sometimes happen is those state messages are for Software Updates.  If you have people who look at reports for Software Updates, and some clients locally say they are Compliant for Software Update KB123456, but when you look at reports based on your database, for that same client, the database says KB123456 on that client is non-Compliant.  Read this: for a much better explanation of why and how; but the short conclusion to that situation is you want to ask your clients to occasionally run what is referred to as a "RefreshServerComplianceState" locally.  Basically, you are asking your clients to resend the state messages about compliant/non-compliant for all existing Software Updates they are aware of locally, to ConfigMgr, your database.  aka.. exactly what it says on the tin.  Refresh Server Compliance State.

The short and sweet is that it's really just a line or two of vbscript or powershell code.  But if you are in a large environment, you often don't want to tell every single client to all send up state messages all on the same day.  It could POTENTIALLY be a lot of data, and backlog your servers' SQL processing.  It would eventually catch up... but why create a headache for yourself?

Below is a Powershell Script that you COULD choose to run... as a set-it-and-forget-it type of thing.  As-is, if you took the below and deployed it as a Powershell Script in a Configuration Item, and the Baseline were to run daily, your clients would around about 2x a year or so randomly decide to RefreshServerComplianceState .  If you want it more frequent; change the 180 to say... 90  (to be about every 3 months, or 60 to be about every 2 months). 

The below is just a suggestion, and you can take it and break it as you like.

  This routine is to generate a random number between 1 and "MaximumRandom". In general, it a Maximum Random
  number will likely be 180; if the Configuration Item is run daily, approximately twice a year it is expected
  that a client will randomly pick a value of 1, and trigger a RefreshServercomplianceState
  - This script would likely be used by a Configuration Manager Administrator as a 'Configuration Item', as the
    "Detection" script in that Configuration Item. The Administrator would set it up as a detect-only script, where
    the "what means compliant" is that any value at all is returned.
  - The Configuration Manager Administrator would likely add this to a baseline, and deploy that baseline to run
    on a Daily basis to their windows-os based devices, which scan for or deploy patches using the Software Updates Feature.
  - Using the MaximumRandom number of 180, presuming the baseline runs daily, approximately twice a year based on
    random probabilities, a client will trigger to run the "ResetServerComplianceState". See the blow mentioned
    below for why this is something a Configuration Manager Administrator might want to do this.
  - If the Configuration Manager Administrator wants to make it randomly occur more frequently or less frequently,
    they would either adjust the $MaximumRandom number higher or lower, or modify the frequency of the Baseline evaluation
  - For interactive testing, modify $VerbosePreference to 'Continue' to see what action was taken. Remember to change
    it back to SilentlyContinue for live deployments.
  - If a client does trigger, an EventLog entry in the ApplicationLog with an Information EventId of 555 from SyncStateScript
    will be created. You can add or modify the -Message entry for the EventLog to be as verbose as you need it to be for
    your own potential future tracking purposes. Perhaps you might want to add in specifics like "Configuration Item
    Named <whatever> in the Baseline <whatever> triggered this action, this was originally deployed on <Date>"

   Credits: Garth Jones for the idea.
   for the reasons why it's a good idea to do so occasionally.
  2018-05-06 Sherry Kissinger

  $VerbosePreference options are
   'Continue' (show the messages)
   'SilentlyContinue' (do not show the message, this is the default if not set at all)
   'Stop' Show the message and halt (use for debugging)
   'Inquire' Prompt the user if ok to continue

Param (
  $VerbosePreference = 'SilentlyContinue',
  $ErrorActionPreference = 'SilentlyContinue',
  $MaximumRandom = 180,
  $ValueExpected = 1
  #ValueExpected Will likely always be 1, and never change; set as a parameter for ease of reporting.

$RandomValue = Get-Random -Maximum $MaximumRandom -Minimum 1
if ($RandomValue -eq $ValueExpected ) {
  Write-Verbose "Random generated value of $RandomValue equals $ValueExpected, therefore RefreshServerComplianceState for ConfigMgr Client State Messages for Updates will be triggered."
  $SCCMUpdatesStore = New-Object -ComObject Microsoft.CCM.UpdatesStore
  New-EventLog -LogName Application -Source SyncStateScript -ErrorAction SilentlyContinue
  Write-EventLog -LogName Application -Source SyncStateScript -EventId 555 -EntryType Information -Message "Configuration Manager RefreshServerComplianceState Triggered to Run. If questions on what this is for, refer to "


  Write-Verbose "Random generated value was $RandomValue, which does not equal $ValueExpected, RefreshServerComplianceState for ConfigMgr Client State Messages for Updates was not triggered. "

Write-Host 'Compliant' 


  • Created on .
Copyright © 2022 - The Twin Cities Systems Management User Group