Install ConfigMgr Client on Azure VM’s without logging in | Azure [RunPowerShellScript] Feature


WINTEL Folks!! How are you doing? 🙂

This post is mainly to help Wintel folks who are managing servers on Azure Cloud. I started working on a new project which focus mainly on Azure. So we started client installation on those VM’s to manage clients centrally from ConfigMgr. I was able to understand the pain of Wintel folks who try to install ConfigMgr client on VM’s one by one or by Script by logging into any one/ VM’s.

Note: We haven’t configured any GPO for client installation and we don’t have direct access to perform client push installation from console.

Being lazy guy who hates manual installation of client, I thought of using PowerShell for client installation. I was assessing the infrastructure and came to know we have multi-forest/domain environment with incoming/outgoing trust.

I started using RunPowerShellScript feature from Azure portal instead of logging into VM’s. This helped me to eliminate manual efforts. Since we have very vast environment, we have multiple subscription and resource groups in Azure. I used few logic to select resource group automatically.

Script has been created purely based on wintel / azure admin logic/view. Folks who are having expertise on ConfigMgr might be using multiple ways to install client like Console Push, GPO, via Software-Update and Scripts.

Use Case:

Couple of Windows VM’s. Few machines are domain joined and few are not. Selected machines have client installed.

I assume you don’t have access neither to ConfigMgr nor Group Policy.

Process-Flow:

ClientInstallation

Script-Flow:

  • Checks if the machine has client installed. If yes, exit.
  • Checks if the machine is part of domain, if yes, copy setup file and trigger installation, else add host entry in the server then copy file and trigger installation.
  • Exit Process.

ConfigMgr Client Installation [Script]:

Clear-Host
if(test-path -Path "HKLM:\SOFTWARE\Microsoft\SMS\Client\Configuration\Client Properties")
{
"Client Already Installed"
}
else
{
if((gwmi win32_computersystem).partofdomain)
{
"Domain Machine - Installing Client"
Copy-Item '\\SVR1.UNICORN.ORG\client$\ccmsetup.exe' -destination $env:windir\temp -force -verbose
Invoke-Expression -Command:"$env:windir\temp\ccmsetup.exe SMSSITECODE=ONP /mp:SVR1.UNICORN.ORG DNSSUFFIX=ONE.OFC.LOC" -Verbose
}
else
{
"Non-Domain Machine Installing Client after adding Host Entries"
Add-Content "$env:systemroot\system32\drivers\etc\hosts" "10.0.0.11 SVR1.UNICORN.ORG" -Force -Verbose
Copy-Item '\\SVR1.UNICORN.ORG\client$\ccmsetup.exe' -destination $env:windir\temp -force -verbose
Invoke-Expression -Command:"$env:windir\temp\ccmsetup.exe SMSSITECODE=ONP /mp:SVR1.UNICORN.ORG dnssuffix=UNICORN.ORG" -Verbose
}
}

Note:

You have to save this script on the local disk of the machine from where you run Invoke-AzureRmVMRunCommand.

In this script I've used share path for client setup, you can use azure blob storage and fetch files across VM's.

Script to Initiate Installation:

Clear-Host
$CSV = IMPORT-CSV 'C:\USERS\g.praveen\Downloads\AzureSuscription.csv'
foreach($line in $csv)
{
$rg = $line.Az_ResourceGroup
$name = $line.Az_Name
if($line.Az_Status -eq "VM running" -and $line.Az_OSType -eq "Windows")
{
"Running for $($name)"| Tee-Object $env:windir\temp\ExecutionLog.log -Append
Invoke-AzureRmVMRunCommand -ResourceGroupName "$rg" -VMName "$name" -CommandId 'RunPowerShellScript' -ScriptPath 'C:\Users\g.praveen\PSScripts\ReplaceCCMClient.ps1' -Verbose | Tee-Object $env:windir\temp\ExecutionLog.log -Append
}
else
{
"VM $($name) is not matching client installation criteria, Aborting action"| Tee-Object $env:windir\temp\ExecutionLog.log -Append
}
}

For performing this action, I used our inventory stored in CSV file and added few logic into it. Basically, the VM should be Windows and it should be in running state.

Pre-Requisite:

  • Your system must have Azure PowerShell module installed.
  • You need to login into Azure using PowerShell.
  • You need to select subscription if you want to perform client installation on few selected ones.

Hope this helps someone who's in need !🙂

-Praveen

 

Advertisements

DP Content Availability | URL Check | ConfigMgr | PowerShell | SQL | Fix Error Code 404


Hello All,

Playing with ConfigMgr Content Packages is always fun😉.

Last Friday, I was working on a OSD failure INC which was pointing towards content unavailability on one of the Distribution Point provisioned for a location, I was looking into the logs from the failed machine, I was able to see Error Code: 404 on logs and I was not trusting my eyes, so I went back to Internet Explorer and I tried browsing the same URL, there also I was getting same error response. (i.e.) 404.

To get rid of that error, I manually redistributed the Content(Application/Package). Post I’m able to see that error for few more packages that too one by one. Irony is that, I was able to see that packages are distributed successfully.

Though I was manually redistributing packages to DP’s from console. I thought of proactively checking that content availability URL using PowerShell automation. Then I came up with a script to check URL status code and perform remediation if it’s doable.

 Name Reference
ContentDPMap SQL Table
Invoke-WebRequest PowerShell CMDLET
SMS_DistributionPoint WMI Table

What is being performed in the script?

  • Gets list of available Packages with PackageID, ServerName and with Content URL using ContentDPMap SQL table.
  • Using ForEach checks URL status by Invoke-WebRequest.
  • Logs status code for each package into a log file.
  • If status code points to 404, then it redistributes the package to the failed DP and logs it into the log file.

Do I need to run this script only on CAS/Primary?

No, there is no such dependencies defined. Only thing is you need to have a supported PowerShell versions to run the used cmdlets.

What about the network connectivity b/w the server i’m used for running scripts and those DP’s?

Yes, TCP Port 80 should be allowed b/w source and destination for HTTP URL request to query the status.

Can this script be used for monitoring and trigger ticket to relevant teams?

Yes, this can be doable if your monitoring tool has capability to generate tickets  based on the log filters.

PowerShell Script:

#Name of the CAS/Primary Server
$CAS = "SVR1"
#Relevant Site Code
$SiteCode = "CAS"
$packages = Invoke-Sqlcmd -Query "select ContentID,ServerName,URL from ContentDPMap where AccessType = 1" -Database "CM_$SiteCode" -HostName "$CAS"
foreach ($package in $packages)
{
$PkgID = $package.ContentID
$DP = $package.ServerName
$URL = $package.URL
$URLCheck = Invoke-WebRequest -Uri "$URL" -UseDefaultCredentials
"$(Get-Date -DisplayHint DateTime) | Checking $($PkgID) for $($DP) on URL $($URL) | Current Status - $($URLCheck.StatusDescription) , Status Code - $($URLCheck.StatusCode)" | Tee-Object -FilePath "$env:windir\temp\URLCheck.log" -Append
if ($URLCheck.StatusCode -eq "400")
{
foreach($refresh in (gwmi -ComputerName "$CAS" -Namespace "root/sms/site_$SiteCode" -Query "select * from sms_distributionpoint where packageid like '$PkgID' and servernalpath like '%$dp%'"))
{
"$(Get-Date -DisplayHint DateTime) | Refreshing $($PkgID) on $($dp)"
$refresh.RefreshNow = $true
$refresh.Put() | Out-Null
"$(Get-Date -DisplayHint DateTime) | Redistributed $($PkgID) for $($DP)" | Tee-Object -FilePath "$env:windir\temp\URLCheck.log" -Append
}
}
elseif($URLCheck.StatusCode -eq "200")
{

}
else
{
"$(Get-Date -DisplayHint DateTime) | Try fixing $($PkgID) on $($DP) manually" | Tee-Object -FilePath "$env:windir\temp\URLCheck.log" -Append
}
}

Hope I’m able to help someone! Feel free to comment for queries/concerns.

-Praveen

What happens when you Reassign a DP from site to site ?| ConfigMgr Internals | SCCM | SQL

Hello All,

I was recently working on one of the recent feature of ConfigMgr [Reassign Distribution Point], this was the feature which all ConfigMgr Admins was eagerly waiting for!

This article from Microsoft explains how this feature works and considerations as well. https://docs.microsoft.com/en-us/sccm/core/servers/deploy/configure/install-and-configure-distribution-points#bkmk_reassign

I thought of sharing a basic detail about how it works in background, so I did captured few log entries and few details from SQL sproc. to explain things.

Ok, lets have a deep dive on some internal stuff like what happens when you reassign a distribution point from SCCM Console.

Once you clicked reassign distribution point, it does 3 pre-checks.

  • Is the DP hosting any additional role other than distribution point?
  • Is the DP hosted in Cloud like (Intune or Azure) as Cloud Distribution Point?
  • Is the DP part of active migration table?

Log:

SMSProv.log

–[Reassign Distribution Point action initiated from console]
CExtProviderClassObject::DoExecuteMethod ReassignDP SMS Provider 30/1/2019 21:33:11 12464 (0x30B0)
–[Checks whether this DP hosts any additional role]
Running Reassign DP PreReq check. SELECT RoleTypeID From SC_SysResUse WHERE NalPAth = ‘[“Display=\\SVR1.unicorn.org\”]MSWNET:[“SMS_SITE=PR1”]\\SVR1.unicorn.org\’ GROUP BY RoleTypeID HAVING RoleTypeID != 3 AND RoleTypeID != 5 AND RoleTypeID != 14 SMS Provider 30/1/2019 21:33:11 12464 (0x30B0)
–[Checks whether this DP hosted in Cloud]
Running Reassign DP PreReq check. SELECT NALResType From SC_SysResUse WHERE NalPAth = ‘[“Display=\\SVR1.unicorn.org\”]MSWNET:[“SMS_SITE=PR1”]\\SVR1.unicorn.org\’ GROUP BY NALResType HAVING NALResType = ‘Windows Intune’ OR NALResType = ‘Windows Azure’ SMS Provider 30/1/2019 21:33:11 12464 (0x30B0)
–[Checks whether this DP is a part of active migration]
Running Reassign DP PreReq check. SELECT NALPath From v_MIG_MigratedDPs WHERE NalPAth = ‘[“Display=\\SVR1.unicorn.org\”]MSWNET:[“SMS_SITE=PR1”]\\SVR1.unicorn.org\’ SMS Provider 30/1/2019 21:33:11 12464 (0x30B0)
–[Runs SQL Stored Procedure for reassigning distribution point]
Execute sproc. spMoveDistributionPoint: EXEC spMoveDistributionPoint ‘PR2’, ‘[“Display=\\SVR1.unicorn.org\”]MSWNET:[“SMS_SITE=PR1”]\\SVR1.unicorn.org\’ SMS Provider 30/1/2019 21:33:11 12464 (0x30B0)

DistMgr.Log

DP upgrade processing thread: Upgrading DP with ID 50332031. Thread 0x90b4. Used 1 threads out of 50. SMS_DISTRIBUTION_MANAGER 1/30/2019 10:44:03 PM 39116 (0x98CC)
Processing 50332031.INS SMS_DISTRIBUTION_MANAGER 1/30/2019 10:44:03 PM 37044 (0x90B4)
DPID 50332031 – NAL Path [“Display=\\SVR1.unicorn.org\”]MSWNET:[“SMS_SITE=PR2”]\\SVR1.unicorn.org\ , ServerName = SVR1.unicorn.org, DPDrive = , IsMulticast = 0, PXE = 1, RemoveWDS = 0 SMS_DISTRIBUTION_MANAGER 1/30/2019 10:44:03 PM 37044 (0x90B4)
ConfigurePullDP SMS_DISTRIBUTION_MANAGER 1/30/2019 10:44:03 PM 37044 (0x90B4)
NAL Path [“Display=\\SVR1.unicorn.org\”]MSWNET:[“SMS_SITE=PR2”]\\SVR1.unicorn.org\ is a Pull DP SMS_DISTRIBUTION_MANAGER 1/30/2019 10:44:03 PM 37044 (0x90B4)
For server SVR1.unicorn.org processor architecture is x64 SMS_DISTRIBUTION_MANAGER 1/30/2019 10:44:04 PM 37044 (0x90B4)
Successfully modified DP [“Display=\\SVR1.unicorn.org\”]MSWNET:[“SMS_SITE=PR2”]\\SVR1.unicorn.org\ Drizzle Role to 1 SMS_DISTRIBUTION_MANAGER 1/30/2019 10:44:11 PM 36512 (0x8EA0)
DP registry settings have been successfully updated on SVR1.unicorn.org SMS_DISTRIBUTION_MANAGER 1/30/2019 10:44:20 PM 36512 (0x8EA0)
Installing PullDP, check \\SVR1.unicorn.org\SMS_DP$\sms\logs\smsdpprov.log and \\SVR1.unicorn.org\SMS_DP$\sms\logs\pulldp_install.log SMS_DISTRIBUTION_MANAGER 1/30/2019 10:44:41 PM 37044 (0x90B4)
PullDP [“Display=\\SVR1.unicorn.org\”]MSWNET:[“SMS_SITE=PR2”]\\SVR1.unicorn.org\ is marked Installed SMS_DISTRIBUTION_MANAGER 1/30/2019 10:44:41 PM 37044 (0x90B4)

List of SQL tables which are updated during this process:

  • SC_SysResUse
  • SysResList
  • SC_SysResUse_Property
  • SC_SysResUse_PropertyList
  • BoundaryGroupSiteSystem
  • DistributionContentVersion
  • DistributionPoints
  • DistributionStatus
  • DPGroupMembers
  • DPUpgradeStatus
  • PKGServers_G
  • PkgServers_L
  • ContentDistributionMessages
  • ContentDPMap
  • DPInfo
  • StatusMessageAttributes
  • StatusMessageInsStrs
  • DistributionPointMessages
  • RBAC_CategoryMemberships
  • SC_Address_Property
  • SC_Address

You can reassign a distribution point by triggering SQL sproc. manually as well, but it’s not recommended to do so.

SQL Query:

Syntax:

EXEC spMoveDistributionPoint ‘Destination Site Code’, ‘ServerNALPath’

Sample Query:

spMoveDistributionPoint ‘PR2’, ‘[“Display=\\SVR1.unicorn.org\”]MSWNET:[“SMS_SITE=PR1”]\\SVR1.unicorn.org\’

Feel free to reach out in comments for queries/concerns.

-Praveen

 

Reassigning clients to Site | PowerShell | ConfigMgr | CB Feature Automation |SCCM


Hello All,

Hope everyone has plans for this week!😉 If you’re not one of them, then I would like to extend my cheers to you! 🍻 I’m also one of them among you who was/is working with some set of PowerShell script for automating stuffs.

If you’re one of the Org./ConfigMgr Admin who has multi-domain AD forest with Multiple Primaries (a.k.a) Complex Environments, you might be seeing clients are assigned to a different site code instead of which it should be assigned to in actual, also you might be fed up performing remediation using registry methods available from various bloggers!

Link to MSFT docs for more detail on Client Reassignment: Article! I don’t remember the Current Branch version exactly from when support for Reassigning Clients from ConfigMgr console has been introduced.

Pre-Requisites:

  1. You have to be in one of the ConfigMgr Current Branch versions. I use ConfigMgr CB 1802
  2. A computer where ConfigMgr console is installed.
  3. You must be connected to CAS site using PowerShell. How to do?

Pro Tip: If you want to use this script in a fully automated way, you can add few lines of code in this script to connect your CAS site automatically. So, that you can use Windows In-Built Task Schedulers for end to end automation of this script.

For building this script I took scenario of an environment with 1 AD Forest with 4 Domains and 3 ConfigMgr Sites.

Domain SCCM Site Code
D01 S01
D02 S02
D03 & D04 S03

What this script does?

This script checks if the device has client installed and has active reporting state. Post check if client is registered in the relevant Site based on the organization technical architecture, if found incorrect, it performs reassigning client to the relevant site.

How do I identify your script is working?

I tried this in our environment and found clients are getting reassigned, also I’ve added logging in script as well, if still required you can check SMSProv.log on your CAS site to verify whether this actions are being performed.

Manual Log File Location: Temp directory on your Windows installation drive.

File Name: ClientReassignment.log

PowerShell Script:

Clear-Host
Clear-Content $env:windir\Temp\ClientReassignment.log -Force -ErrorAction SilentlyContinue
$Devices = Get-CMDevice
foreach ($Device in $devices)
{
$Domain = $Device.Domain
$SiteCode = $Device.SiteCode
[Array]$ResourceID = @("$($Device.ResourceID)")
$ObjName = $Device.Name
"$(Get-Date -DisplayHint DateTime) | Checking for $ObjName"
"$(Get-Date -DisplayHint DateTime) | Checking for $ObjName" | Out-File $env:windir\Temp\ClientReassignment.log -Append
if ($Device.IsClient -eq "True" -and $Device.IsActive -eq "True")
{
if ($Domain -eq "D01" -and $SiteCode -ne "S01")
{
"$(Get-Date -DisplayHint DateTime) | Reassigning $ObjName to S01 site as per domain"
"$(Get-Date -DisplayHint DateTime) | Reassigning $ObjName to S01 site as per domain" | Out-File $env:windir\Temp\ClientReassignment.log -Append
Invoke-CMWmiMethod -ClassName "SMS_Collection" -MethodName "ReassignClientsToSite" -Parameter @{ResourceIDs = $ResourceID ; NewSiteCode = "S01"} | Out-Null
}
elseif($Domain -eq "D02" -and $SiteCode -ne "S02")
{
"$(Get-Date -DisplayHint DateTime) | Reassigning $ObjName to S02 site as per domain"
"$(Get-Date -DisplayHint DateTime) | Reassigning $ObjName to S02 site as per domain" | Out-File $env:windir\Temp\ClientReassignment.log -Append
Invoke-CMWmiMethod -ClassName "SMS_Collection" -MethodName "ReassignClientsToSite" -Parameter @{ResourceIDs = $ResourceID ; NewSiteCode = "S02"} | Out-Null
}
elseif($Domain -eq "D03" -and $SiteCode -ne "S03")
{
"$(Get-Date -DisplayHint DateTime) | Reassigning $ObjName to S03 site as per domain"
"$(Get-Date -DisplayHint DateTime) | Reassigning $ObjName to S03 site as per domain" | Out-File $env:windir\Temp\ClientReassignment.log -Append
Invoke-CMWmiMethod -ClassName "SMS_Collection" -MethodName "ReassignClientsToSite" -Parameter @{ResourceIDs = $ResourceID ; NewSiteCode = "S03"} | Out-Null
}
elseif($Domain -eq "D04" -and $SiteCode -ne "S03")
{
"$(Get-Date -DisplayHint DateTime) | Reassigning $ObjName to S03 site as per domain"
"$(Get-Date -DisplayHint DateTime) | Reassigning $ObjName to S03 site as per domain" | Out-File $env:windir\Temp\ClientReassignment.log -Append
Invoke-CMWmiMethod -ClassName "SMS_Collection" -MethodName "ReassignClientsToSite" -Parameter @{ResourceIDs = $ResourceID ; NewSiteCode = "S03"} | Out-Null
}
else
{
"$(Get-Date -DisplayHint DateTime) | Performing no actions on $ObjName since it is not matching reassignment criteria"
"$(Get-Date -DisplayHint DateTime) | Performing no actions on $ObjName since it is not matching reassignment criteria" | Out-File $env:windir\Temp\ClientReassignment.log -Append
}
}
else
{
"$(Get-Date -DisplayHint DateTime) | Performing no actions on $ObjName since it is not an active client"
"$(Get-Date -DisplayHint DateTime) | Performing no actions on $ObjName since it is not an active client" | Out-File $env:windir\Temp\ClientReassignment.log -Append
}
}
"$(Get-Date -DisplayHint DateTime) | End of Execution"
"$(Get-Date -DisplayHint DateTime) | End of Execution" | Out-File $env:windir\Temp\ClientReassignment.log -Append

CmdLet References:

CMDLET Reference
Get-CMDevice Article
Invoke-CMWmiMethod Article

 
Feel free to connect with me for queries/concerns.

-Praveen

PowerShell One-Liner to remove dead DP from ConfigMgr hierarchy | WMI | PowerShell


Hello All,

This week, I was working on one of the tasks for removing dead DP from a ConfigMgr hierarchy, I was bored of performing actions always from ConfigMgr console, came up with this PowerShell one-liner, it queries WMI class for the availability and deletes them if found.

You can connect to either your CAS/Primary Server remotely from WMI and perform this actions. You’ve to update the below given variables in order to perform this action in your environment.

SVR1 – CAS / Primary Server Name.

PRI – Relevant Site Code.

DP1 – Site Server name which you want to remove from hierarchy.

PowerShell Script:

Get-WmiObject -ComputerName "SVR1" -Namespace "root/sms/site_PRI" -query "select * from SMS_SCI_SYSRESUSE where nalpath like '%DP1%'" | Remove-WmiObject -Verbose

Pro Tip: Prefer running command from CAS site if you have CAS hierarchy in your environment!

Reference Article:

SMS_SCI_SYSRESUSE

Feel free to comment for questions/concerns.

-Praveen

 

SCCM Logs : File Rename Error | Maximum Log File Size Reached : Fix


Hello All,

I was working one of the incident at my workplace which was related to distribution, some packages are not getting distributed to Remote DP’s. I opened PkgXferMgr.log to trace what’s happening with regards to distribution.

I found something like the image given below. Actual threshold of the file was 2 MB, but this file has reached 48 MB and is not getting renamed.

pkgxfermgr

Tried restarting the component and SMS_EXECUTIVE with no success, I was scratching my head and surfing internet to see if someone has encountered similar problem. Didn’t find any relevant articles which directed towards fix.

Resolution:

  • Stop the component which is having issues. Either from Service Manager or from RegEdit
  • Try renaming the file manually.
  • If you get “This action cannot be performed because it is opened in System” error. Follow below.
  • Use fsmgmt.msc disconnect all sessions of the file if opened remotely.
  • Try renaming the file again.
  • Sample Powershell:

    Rename-Item PkgXferMgr.log -NewName "PkgXferMgr.lo_Old" -Force -Verbose 

The above given steps should fix this issue. Happy Troubleshooting!

-Praveen


 

Configuration Baseline for Profile CleanUp | ConfigMgr | WMI | PowerShell


Hello All,

With this post I would like to help my fellow teammates who face Customers/IT BRM’s directly. Who are they? They are the people who support Desktops/Laptops/Servers on a real-time basis. Popularly called as “System Administrators

At times, Administrators may see messages stating low disk space on OS drives which will eventually end up showing disk utilized by user profiles, to mitigate this some will clean old user profiles from file explorer and forget to delete the registry. Post this action some end users might receive a pop-up once they login stating “You have been logged in with a temporary profile!” End users those who don’t know what happened may raise incidents with Service Desk to get this fixed. For automating this I came up with an idea.

Some of the environments may already have startup scripts enabled to do this on daily/weekly/monthly basis. Again I have came up with a ConfigMgr Configuration Baseline to reduce GPO efforts.

What is being done in this script?

  • Log Off all disconnected users.
  • Checking registry path [HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList] to see if any keys present with names ending .bak
  • Cleanup those registry keys.
  • Check for domain profiles with last login greater than 90 days.
  • Delete those profiles which matches the criteria.

P.S: Actions performed by this script is being logged via log file in Windows Temp directory. a.k.a %windir%\temp

Discovery Script [PowerShell]:

Clear-Host
$oldprof = $null
quser | Select-String "Disc" |ForEach {logoff ($_.tostring() -split ' +')[2]}
Clear-Content $env:windir\temp\profilecleanup.log -Force -ErrorAction SilentlyContinue
$regpath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"
#"$(Get-Date -DisplayHint DateTime) | Performing cleanup of bad registry entries on $regpath" | Out-File $env:windir\temp\profilecleanup.log -Append
foreach ($regkey in (Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\" -Recurse))
{
if ($regkey.Name -like "*.bak")
{
$cleanup = $regkey.Name
#"$(Get-Date -DisplayHint DateTime) | Removing $($cleanup)" | Out-File $env:windir\temp\profilecleanup.log -Append
#reg delete "$cleanup" /f
}
}
"$(Get-Date -DisplayHint DateTime) | Performing old user cleanup where last login time > 90 days" | Out-File $env:windir\temp\profilecleanup.log -Append
$users = Get-WmiObject win32_userprofile
$days = "90"
foreach ($user in $users)
{
$SID =$($user.sid)
$objSID = New-Object System.Security.Principal.SecurityIdentifier($SID)
$objUser = $objSID.Translate([System.Security.Principal.NTAccount])
if (($user.LastUseTime -lt $(Get-Date).Date.AddDays(-$days))-and $user.sid -like "S-1-5-21-*")
{
$oldprof++
#"$(Get-Date -DisplayHint DateTime) | Removing $($objUser.value)" | Out-File $env:windir\temp\profilecleanup.log -Append
#$user.Delete()
}
else
{
#"$(Get-Date -DisplayHint DateTime) | Skipping $($objUser.value)" | Out-File $env:windir\temp\profilecleanup.log -Append
}
}
if ($oldprof -ge "1")
{
return $false
}
else
{
return $true
}

Remediation Script [PowerShell]:

Clear-Host
quser | Select-String "Disc" |ForEach {logoff ($_.tostring() -split ' +')[2]}
Clear-Content $env:windir\temp\profilecleanup.log -Force -ErrorAction SilentlyContinue
$regpath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"
"$(Get-Date -DisplayHint DateTime) | Performing cleanup of bad registry entries on $regpath" | Out-File $env:windir\temp\profilecleanup.log -Append
foreach ($regkey in (Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\" -Recurse))
{
if ($regkey.Name -like "*.bak")
{
$cleanup = $regkey.Name
"$(Get-Date -DisplayHint DateTime) | Removing $($cleanup)" | Out-File $env:windir\temp\profilecleanup.log -Append
reg delete "$cleanup" /f
}
}
"$(Get-Date -DisplayHint DateTime) | Performing old user cleanup where last login time > 90 days" | Out-File $env:windir\temp\profilecleanup.log -Append
$users = Get-WmiObject win32_userprofile
$days = "90"
foreach ($user in $users)
{
$SID =$($user.sid)
$objSID = New-Object System.Security.Principal.SecurityIdentifier($SID)
$objUser = $objSID.Translate([System.Security.Principal.NTAccount])
if (($user.LastUseTime -lt $(Get-Date).Date.AddDays(-$days))-and $user.sid -like "S-1-5-21-*")
{
"$(Get-Date -DisplayHint DateTime) | Removing $($objUser.value)" | Out-File $env:windir\temp\profilecleanup.log -Append
$user.Delete()
}
else
{
#"$(Get-Date -DisplayHint DateTime) | Skipping $($objUser.value)" | Out-File $env:windir\temp\profilecleanup.log -Append
}
}

We don’t have access to ConfigMgr, How to implement this via GPO?

Same script can be used with some modifications.

GPO Script [PowerShell]:

Clear-Host
quser | Select-String "Disc" |ForEach {logoff ($_.tostring() -split ' +')[2]}
Clear-Content $env:windir\temp\profilecleanup.log -Force -ErrorAction SilentlyContinue
$regpath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"
"$(Get-Date -DisplayHint DateTime) | Performing cleanup of bad registry entries on $regpath" | Out-File $env:windir\temp\profilecleanup.log -Append
foreach ($regkey in (Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\" -Recurse))
{
if ($regkey.Name -like "*.bak")
{
$cleanup = $regkey.Name
"$(Get-Date -DisplayHint DateTime) | Removing $($cleanup)" | Out-File $env:windir\temp\profilecleanup.log -Append
reg delete "$cleanup" /f
}
}
"$(Get-Date -DisplayHint DateTime) | Performing old user cleanup where last login time > 90 days" | Out-File $env:windir\temp\profilecleanup.log -Append
$users = Get-WmiObject win32_userprofile
$days = "90"
foreach ($user in $users)
{
$SID =$($user.sid)
$objSID = New-Object System.Security.Principal.SecurityIdentifier($SID)
$objUser = $objSID.Translate([System.Security.Principal.NTAccount])
if (($user.LastUseTime -lt $(Get-Date).Date.AddDays(-$days))-and $user.sid -like "S-1-5-21-*")
{
"$(Get-Date -DisplayHint DateTime) | Removing $($objUser.value)" | Out-File $env:windir\temp\profilecleanup.log -Append
$user.Delete()
}
else
{
#"$(Get-Date -DisplayHint DateTime) | Skipping $($objUser.value)" | Out-File $env:windir\temp\profilecleanup.log -Append
}
}

Kindly drop your queries/concerns in comments section.

Happy to Help!!

-Praveen