Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the twentyseventeen domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /var/www/html/wp-includes/functions.php on line 6121
SCCMOG – Deployment Blog – Page 2 – Everything and anything deployment related!

Disable RDP Windows 10 PowerShell Script Configuration Baseline SCCM

So I was setting up a KIOSK environment using  Windows 10 1709 for a client recently and we wanted to take the route of applying as few GPOs as possible (as it should be in 2018)!

Ensuring that this stayed disabled was something that we decided to deploy using ConfigMgr Configuration Baselines.

So the Check compliance script is as follows:

##################################################################################################################
#
#  Author: Richie Schuster - C5 Alliance - SCCMOG.com
#  Date:   06/07/2018
#  Script: Action-CheckRDPCompliance.ps1
#  Usage: Powershell.exe -ExecutionPolicy Bypass -File .\Action-CheckRDPCompliance.ps1
#
##################################################################################################################

#Variables
$TSRegPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server"
$TSRegProperty = "fDenyTSConnections"
$RDPTcpRegPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp"
$RDPTcpRegProperty = "UserAuthentication"

#Set initial values
$TSSet = $True
$RDPTCPSet = $True
$RDPUserinTCPReturn = $True
$RDPUserinUDPReturn = $True
$RDPShadinTCPReturn = $True

#Test fDenyTSConnections state
$TSReturn = (Get-ItemProperty -Path $TSRegPath -Name $TSRegProperty -ErrorAction SilentlyContinue).fDenyTSConnections
If ($TSReturn -eq 1) {
    $TSSet = $false
}

#Test RDP-TCP State
$RDPTCPReturn = (Get-ItemProperty -Path $RDPTcpRegPath -Name $RDPTcpRegProperty -ErrorAction SilentlyContinue).UserAuthentication
If ($RDPTCPReturn -eq 0) {
    $RDPTCPSet = $false
}

#Get Firewall states
$RDPUserinTCPReturn = (Get-NetFirewallRule -Name $RDPUserinTCP -ErrorAction SilentlyContinue).Enabled
$RDPUserinUDPReturn = (Get-NetFirewallRule -Name $RDPUserinUDP -ErrorAction SilentlyContinue).Enabled
$RDPShadinTCPReturn = (Get-NetFirewallRule -Name $RDPShadinTCP -ErrorAction SilentlyContinue).Enabled

#Evaluate and report
If (!($RDPUserinTCPReturn) -and ($RDPUserinUDPReturn) -and ($RDPShadinTCPReturn) -and ($TSSet) -and ($RDPTCPSet)) {
    Write-Host "Compliant!"
}

##################################################################################################################

Ok, so now the check script is out the way, here is the remediation script:

##################################################################################################################
#
#  Author: Richie Schuster - C5 Alliance - SCCMOG.com
#  Date:   06/07/2018
#  Script: Action-RemediateRDPCompliance.ps1
#  Usage: Powershell.exe -ExecutionPolicy Bypass -File .\Action-RemediateRDPCompliance.ps1
#
##################################################################################################################

#Variables
$TSRegPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server"
$TSRegProperty = "fDenyTSConnections"
$RDPTcpRegPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp"
$RDPTcpRegProperty = "UserAuthentication"

#Remediate and Block RDP
Set-ItemProperty $TSRegPath -Name $TSRegProperty -Value 1 -Force
Set-ItemProperty $RDPTcpRegPath -Name $RDPTcpRegProperty -Value 0 -Force
Disable-NetFirewallRule -DisplayGroup "Remote Desktop"

#The End :)
##################################################################################################################

As Always scripts are as is, and if you do use them remeber where you got them from 😉

If you would like to see the setup of this baseline let me know in the comments below.

Cheers,

SCCMOG

OSD Task Seqeunce High Performance – Native PowerCFG – VBS – MDT -SCCM

So like a lot of ConfigMgr Admins out there I strive to produce the fastest and most robust task sequences I can. This obviously led me down the path of configuring the Power Settings on the OS during deployment and capture phases.

The issue that I found is probably like many of you have and that is that it requires exporting the native “PowerCFG.exe” from each version of the OS that you would like to dynamically change the power configuration during Operating System Deployment.

So Jack and I were discussing why… in short… do we not use the “PowerCFG.exe” that is is already in the OS that we are configuring. Anyway after a little persuasion we came up with this little beauty of a script. This script will also create a CMTrace “Log”  using its name in the Log folder that is currently in use in the Task sequence.

Setup is simple:

MDT Setup
  • For MDT just place it into the Scripts folder of you “Deployment Share”.”
    "<YourDeploymentShareDrive>:\<DeploymentShareRoot>\Scripts\Set-PowerCFG.wsf"
  • You can then call this during any stage of you Reference image capture i.e. WinPE or full OS phase to set the power cfg with these below Command Lines:
    "%ScriptRoot%\Set-PowerCFG.wsf" /High
    "%ScriptRoot%\Set-PowerCFG.wsf" /Bal
SCCM Setup
  • For SCCM you must be using the MDT integration (if you’re not… Start now!), you can make it work without it but I will not cover that here.
  • Find your current MDT Toolkit Package that is associated with the Task Sequence you would like to configure power settings in.
  • Open the “Source” location of your toolkit package, then open the scripts folder.

  • Once inside the scripts folder copy the “Set-PowerCFG.wsf” into it.

    • Now, update the Package in ConfigMgr.
    • Next we need to add the step to the task sequence. It must go after the “Use Toolkit Package” step in the task sequence. (If you have a reboot and remember to add another use “Toolkit package”.)
    • Create a new “Run Command Line Step” and add one of the below commands.
cscript.exe "%deployroot%\scripts\Set-PowerCFG.wsf" /High
cscript.exe "%deployroot%\scripts\Set-PowerCFG.wsf" /Bal

And that is it. Your Task sequence will now set the Power Configuration Profile using the current OS’s native “Powercfg.exe”

Anyway as always, script is provided as is and if you do mod it, there is a line to add your name.

The Script:

<job id="Set-PowerCFG">
<script language="VBScript" src="ZTIUtility.vbs"/>
<script language="VBScript">
 
' //***************************************************************************
' // ***** Script Header *****
' //
' // Solution: Solution Accelerator for Microsoft Deployment
' // File: Set-PowerCFG.wsf
' // Requirements: ZTIUtility.vbs Must be located in the same folder id running from ConfigMGR.
' // Author: SCCMOG Richie Schuster - SCCMOG.com
' // Moddedby: YOURNAMEHERE
' // Date: 21/03/2018
' // Purpose:  Script sets power profile during OS deployment - Can be run in WinPE or full OS.
' //
' // Usage: cscript Set-PowerConfig.wsf [/High] - Sets high performance | [/Bal] - Sets balanced performance. [/debug:true] 
' //
' // Customer Build Version: 1.0.0
' // Customer Script Version: 1.0.0
' // Customer History:
' //
' // ***** End Header *****
' //***************************************************************************
 
'//----------------------------------------------------------------------------
'//
'// Global constant and variable declarations
'//
'//----------------------------------------------------------------------------
 
'Option Explicit
Dim strPwrBalanced	: strPwrBalanced	= "381b4222-f694-41f0-9685-ff5bb260df2e"
Dim strPwrHighPerf	: strPwrHighPerf	= "8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c"
Dim strPwrCFGexe	: strPwrCFGexe		= "powercfg.exe"
Dim strPwrCFGswitch : strPwrCFGswitch	= " /S "
Dim strPwrCFGFolder : strPwrCFGFolder	= "PowerCFG"
Dim strWmessage		: strWmessage		= "Please enter one or more arguments 'E.G : /High for High Performance Profile' or '/Bal for Balanced Profile'"
Dim strWindir		: strWindir			= oShell.ExpandEnvironmentStrings("%Windir%")
Dim strSysWow64		: strSysWow64		= "SysWOW64"
Dim strSystem32		: strSystem32		= "System32"

Dim  PowerCFG, strPwrProfile, strPwrProfileLog, ostrCMD, iRetVal
 
'//----------------------------------------------------------------------------
'// End declarations
'//----------------------------------------------------------------------------

'//----------------------------------------------------------------------------
'// Main routine
'//----------------------------------------------------------------------------
'Begin
oLogging.CreateEntry "Beginning Set-PowerCFG 1.0.0", LogTypeInfo
oLogging.CreateEntry "Active Log Path: " & oUtility.LogPath, LogTypeInfo

'Take Script Args
oLogging.CreateEntry "Getting set Arguments...", LogTypeInfo
If Not WScript.Arguments.Count >= 1 Then
	oLogging.ReportFailure strWmessage, iError
End If 
If WScript.Arguments.Named.Exists("High") Then 
	strPwrProfile = strPwrHighPerf
	strPwrProfileLog = "High"
	oLogging.CreateEntry "Power Profile requested to be set: " & strPwrProfileLog, LogTypeInfo
ElseIf WScript.Arguments.Named.Exists("Bal") Then 
	strPwrProfile = strPwrBalanced
	strPwrProfileLog = "Balanced"
	oLogging.CreateEntry "Power Profile requested to be set: " & strPwrProfileLog, LogTypeInfo
Else
	oLogging.ReportFailure strWmessage, iError
End If

'OS Info for Log
oLogging.CreateEntry "Getting information on Operating System", LogTypeInfo
oLogging.CreateEntry "OS Name: " & oEnvironment.Item("OSVERSION"), LogTypeInfo
oLogging.CreateEntry "OS Version: " & oEnvironment.Item("OSCURRENTVERSION"), LogTypeInfo
oLogging.CreateEntry "OS Architecture: " & oEnvironment.Item("Architecture"), LogTypeInfo

'Get OS Arch and set folder to run powerCFG from.
If oEnvironment.Item("Architecture") = "X86" Or oEnvironment.Item("_SMSTSInWinPE") = "true" Or oEnvironment.Item("OSVERSION") = "WinPE" Then
	strPwrCFGFolder = strSystem32
	oLogging.CreateEntry "Using PowerCFG.exe from: " & strPwrCFGFolder & " Folder.", LogTypeInfo
Elseif oEnvironment.Item("Architecture") = "X64" Then
	strPwrCFGFolder = strSysWow64
	oLogging.CreateEntry "Using PowerCFG.exe from: " & strPwrCFGFolder & " Folder.", LogTypeInfo
Else
	oLogging.ReportFailure "Machine Architecture not supported.", iError
End If

'Setting PowerCFG Location
strCMD = strWindir &  "\" & strPwrCFGFolder & "\" & strPwrCFGexe

'Logging Command
oLogging.CreateEntry "PowerCFG Location set to: " & strCMD, LogTypeInfo
oLogging.CreateEntry "Testing Location Available..." & strCMD, LogTypeInfo
iRetVal = oFso.FileExists(strPath)

If iRetVal = Success Then
	oLogging.CreateEntry "Found PowerCFG.exe at Location: " & strCMD, LogTypeInfo
	strCMD =  Chr(34) & strCMD &  Chr(34)
	oLogging.CreateEntry "Building Command to be run", LogTypeInfo
	strCMD = strCMD & strPwrCFGswitch & strPwrProfile
	oLogging.CreateEntry "Final Command to be run: " & strCMD, LogTypeInfo
	'Run PowerCFG
	iRetVal = oUtility.RunWithHeartbeat(strCMD)
	If iRetVal = Success Then
		oLogging.CreateEntry "Successfully set " & strPwrProfileLog & " Performance profile.", LogTypeInfo
		oLogging.CreateEntry "Set-PowerCFG 1.0.0 Complete...", LogTypeInfo
	Else
		oLogging.ReportFailure "Failed to set " & strPwrProfileLog & " Performance profile.", iError
	End If
Else
	oLogging.ReportFailure "Failed to find path: " & strCMD, iError
End If

	</script>
</job>

Cheers,

SCCMOG

Bulk Discover Client Versions

So this morning I was checking up on the status of some client upgrades after installing the latest 1710 hotfix. WMI on each machine had updated to reflect the latest client versions, however most machines hadn’t reported back to ConfigMgr so they were still listed as the older version. Being impatient, I wrote a script that I could use with SCCMs ‘Run Script‘ feature (available as a pre-release feature from version 1706) that would scrape WMI on each machine locally and report back the client version… simple, but awesome!

The script is as follows:

#############################################################
#
#  Author: Jack O'Connor
#  Website: https://github.com/JackOconnor21
#  Modified By: Your name here...
#  Description: Simple script to use on a local machine or via SCCMs
#     'run script' feature to find the client version of a machine
#     in the event SCCM has not yet updated the machine records.
#  
#  Edit the $CurrClientVer variable to reflect the latest client version
#  which is what we will test against.
#
##############################################################

$CurrClientVer = "5.00.8577.1115"
$MyClientVer = (Get-WMIObject -Namespace root\ccm -Class SMS_Client).ClientVersion

If ($MyClientVer -eq $CurrClientVer) {
    Write-Host "Up to date" -ForegroundColor Green
} Else {
    Write-Host "Out of date ($MyClientVer)" -ForegroundColor Red
}

The idea is you add the new client version number into the $CurrClientVer and the script will check WMI on each machine and match the client version against that – if it matches then we get an “Up to date” output, but if it does not we get an “Out of date” output followed by the actual client version on that machine.

You can run this script on any collection you like, I chose to run it on my “System Health | Clients Active” collection, which gave me the feedback pictured below.

 

 

 

 

 

Create Device Collections From Active Directory OUs with PowerShell

I was setting up a Config Manager environment for a client who is situated in roughly 40 locations. Each location had an Organizational Unit (OU) in Active Directory (AD) and within that OU was… even more OUs! This was understandable as in each location there were many different rooms for many different purposes and so these were organised via separate OUs.

Baring in mind 40 locations had many OUs and I needed to create a device collection which houses all corresponding devices for each of these, I had to turn to my good friend PowerShell as there is no chance I was creating 400+ device collections manually! As a result I create this script.

################################################################################
#
#   Author: Jack O'Connor
#   Description: Create ConfigMgr Device Collections from Active Directory Organisational Units and Computers
#   Website: https://github.com/JackOconnor21
#
#   Params: OUSearchBase, LimitingCollection, MembershipRule, CollectionFolder
#   Example: .\Create-CollectionsFromOUs.ps1 -OUSearchBase "OU=Workstations,OU=Internal IT,OU=JACKO,DC=JACKO,DC=LOCAL" -LimitingCollection "All Systems" -ExcludedOUS "Admin Accounts, Users" -MembershipRule "Query" -CollectionFolder "Test" -Tag "EUR"
#   Logging Keys:
#      - Green: Success, Red: Failure, Cyan: General (Info)
#
#   Docs:
#    OUSearchBase:
#      The children of the OU passed as the OUSearchBase will have device collections created with their names.
#    LimitingCollection:
#      The LimitingCollection is self-explanitory, it is simply the limiting collection of the device collections that are being created.
#    ExcludedOUs:
#      Here we can define any OUs that we wish to explicitly exclude and not create a device collection for. (typically this is OUs which contain accounts)
#    MembershipRule:
#      The MemberShipRule can be either Query or Direct. If 'Query' then all devices from the OUs will be pulled into the device collection, now and in the future (due to the query on the collection).
#      If the MembershipRule is Direct then all members in the OUs will be pulled in one time only, but not any future devices.
#    CollectionFolder:
#      The value you pass here as a param will be the folder your new device collections will be added to. E.G. "Operational"
#    Tag:
#      Optionally tag the device collection with a name. This will appear as such 'OU Based | $Tag | CollectionName' (for example a site code could be used if you have many sites)
#   
################################################################################
Param(
    [Parameter(Mandatory=$true)][String]$OUSearchBase,
    [Parameter(Mandatory=$true)][String]$LimitingCollection,
    [String]$ExcludedOUs,
    [Parameter(Mandatory=$true)][String]$MembershipRule,
    [Parameter(Mandatory=$true)][String]$CollectionFolder,
    [String]$Tag
)

# Check for elevation
Write-Host "Checking for elevation"
If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(`
    [Security.Principal.WindowsBuiltInRole] "Administrator"))
{
    Write-Warning "Please run from elevated PowerShell prompt!"
    Write-Warning "Aborting script..."
    Break
}

$Path = Get-Location

 # Import the ConfigurationManager.psd1 module 
if((Get-Module ConfigurationManager) -eq $null) {
    Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1"
}

$SiteCode = (Get-PSDrive -PSProvider CMSITE).Name

Import-Module ActiveDirectory
Write-Host "Imported the 'ActiveDirectory' module" -ForegroundColor Cyan

Set-Location "$SiteCode`:"

# Create a schedule to run every 4 hours.
$Schedule = New-CMSchedule –RecurInterval Hours –RecurCount 4

# Get all OUs that are one level below the given OU as the search base
$OUs = Get-ADOrganizationalUnit -LDAPFilter "(name=*)" -SearchScope OneLevel -SearchBase $OUSearchBase

# Convert the string of comma-separated excluded OUs to an array
If($ExcludedOUs) {
    $ExcOUArr = $ExcludedOUs.Split(",").Trim()
}

If ($Tag) { 
    $Tag = " $Tag |" 
} Else { 
    $Tag = "" 
}

# Loop each of the OUs that we have found above
Foreach ($OU in $OUs) {

    # Skip doing anything with OUs and Computers that have been excluded
    if ($ExcOUArr -Contains $OU.Name) { 
        Write-Host "Excluding '$($OU.Name)' from being processed." -ForegroundColor Gray
        continue 
    }

    Write-Host "Looping over the '$($OU.Name)' OU" -ForegroundColor Cyan

    # Create a new device collection within SCCM with the given parameters
    try {
        $currCollection = New-CMDeviceCollection -Name "OU Based |$Tag $($OU.Name)" -LimitingCollectionName $LimitingCollection -RefreshSchedule $Schedule -RefreshType Periodic
        Write-Host "New ConfigMgr Device Collection created with the name '$($OU.Name)'" -ForegroundColor Green

        # Allows for the passing of a collection folder which is the folder your created device collections will be moved into.
        $CollectionPath = $SiteCode + ":\DeviceCollection\$CollectionFolder"

        # Move the device collection into the chosen collection folder.
        Move-CMObject -FolderPath $CollectionPath -InputObject (Get-CMDeviceCollection -Name $currCollection.Name)

        # Set is error to false to indicate no error has occurred.
        $isError = 0
    } catch {
        Write-Host "Error creating Device Collection '$($OU.Name)' - This Device Collection may already exist. Any new found devices will still be added." -ForegroundColor Red
        $isError = 1
    }

    # If the membership rule is query and there has not been an error.
    If ($MembershipRule -eq "Query" -AND !$isError) {

        # Convert the OU Distinguished name to the canonical name of the OU.
        $CanonicalOUName = Get-CanonicalName($OU.DistinguishedName)

        # The device collection query will differ based on which members are to be added.
        $Query = 'SELECT *  FROM  SMS_R_System WHERE SMS_R_System.SystemOUName = "' + $($CanonicalOUName) + '"'

        # Add the query membership rule to the Device Collection
        Add-CMDeviceCollectionQueryMembershipRule -CollectionId $currCollection.CollectionId -QueryExpression $Query -RuleName $OU.Name

    } ElseIf ($MembershipRule -eq "Direct") {

        # Get all computers that exist beneath the current OU at any level
        $ADComps = Get-ADComputer -LDAPFilter "(name=*)" -SearchBase "OU=$($OU.Name),$OUSearchBase" -SearchScope Subtree

        # Loop each of the computers beneath the current OU within AD
        Foreach ($Comp in $ADComps) {
            Write-Host "Looping over all AD computer '$($Comp.Name)' within the $($OU.Name) OU" -ForegroundColor Cyan

            # Get the resource ID of the current computer
            $ResourceID = $(Get-CMDevice -Name $Comp.Name).ResourceID 

            # Add the current computer device into the current device collection based on the resource ID of the machine
            try {
                Add-CMDeviceCollectionDirectMembershipRule -CollectionName "OU Based |$Tag $($OU.Name)" -ResourceId $ResourceID
                Write-Host "AD computer '$($Comp.Name)' added to the '$($OU.Name)' ConfigMgr Device Collection" -ForegroundColor Green
            } catch {
                 Write-Host "Error adding device '$($Comp.Name)' to collection '$($OU.Name)' - This device may already exist within the collection." -ForegroundColor Red
            }
        }
    }
}

Set-Location $Path

# Function by thepip3r @ https://gallery.technet.microsoft.com/scriptcenter/Get-CanonicalName-Convert-a2aa82e5
function Get-CanonicalName ([string[]]$DistinguishedName) {    
    foreach ($dn in $DistinguishedName) {      
        $d = $dn.Split(',') ## Split the dn string up into it's constituent parts 
        $arr = (@(($d | Where-Object { $_ -notmatch 'DC=' }) | ForEach-Object { $_.Substring(3) }))  ## get parts excluding the parts relevant to the FQDN and trim off the dn syntax 
        [array]::Reverse($arr)  ## Flip the order of the array. 
 
        ## Create and return the string representation in canonical name format of the supplied DN 
        "{0}/{1}" -f  (($d | Where-Object { $_ -match 'dc=' } | ForEach-Object { $_.Replace('DC=','') }) -join '.'), ($arr -join '/') 
    } 
}

Although fairly well documented within the script (If i do say so myself), I will run you through it in a bit more detail. Below you can see an example usage of this script.

#
.\Create-CollectionsFromOUs.ps1 -OUSearchBase "OU=Workstations,OU=Internal IT,OU=JACKO,DC=JACKO,DC=LOCAL" -LimitingCollection "All Systems" -ExcludedOUS "Admin Accounts, Users" -MembershipRule "Query" -CollectionFolder "Test" -Tag "EUR"
#

For example, let’s pretend we have an AD OU structure a little something like this:

JACKO.local > JACKO > Internal IT > Workstations

Under workstations, imagine we had the following OUs

  • Meeting Room 1
  • Meeting Room 2
  • Office Ground
  • Office Second
  • Main Office
  • IT Office

Now picture each of these OUs having multiple devices and we need to create a device collection for each of them and add the devices within them to that collection. If there are only 6 like above then it’s an easy job and you might consider doing this manually at first, however if there are 50… you might not.

This is where the script comes into play, it is simple to use although it may look daunting at first. There are a couple of question you should ask first:

  1. What do I want the limiting collection of all of these new collections to be?
  2. Are there any OUs I do NOT want to create a collection for?
  3. Do I want to only add the machines that are currently in the OUs to the collection or do I want to also add any devices that are added in the future to the OU?
  4. Do I want these device collections to be in a folder within Config Manager?
  5. Finally, do I want to identify these collections with a tag?

Question #1 corresponds to the “LimitingCollection” parameter, simply pass the name of the desired limiting collection.

Question #2 corresponds to the “ExcludedOUs” parameter, pass a comma-separated list of any OU names you do NOT wish to make a collection for.

Question #3 corresponds to the “MembershipRule” parameter, pass “Query” if you want to add all current AND future devices. Pass “Direct” if you only want to add current devices, but avoid adding any new devices added to the AD OU.

Question #4 is based on the “CollectionFolder” param, just pass the path of that folder. E.G. “Operational” or “Operational\Workstations”

Question #5 related to the “Tag” param. By default all these collections will be created with a name like:

OU Based | $CollectionName

as we believe that you should differentiate your collections and label the ones built from OUs. If you use a tag then the name will change slightly to:

OU Based | $Tag | $CollectionName

Tags are useful for separate sites and also avoid duplicate collection names across sites.

Last but not least, the most important parameter is the “OUSearchBase” param which is going to be the name of the OU which parents all of the other OUs you wish to make device collections based off. In our example we would have to the value

“OU=Workstations,OU=Internal IT,OU=JACKO,DC=JACKO,DC=LOCAL”

This would loop all OUs that are children of “Workstations” and create a device collection for each and automate all that hard work so you can sit back, relax, and have a coffee.

Copyright 2016 SCCMOG | All Rights Reserved