SCCM – Automatically Protecting Windows File Servers From Ransomware Using FSRM

Hi folks,

Perhaps the title is a bit misleading.  This is by no means the only protection to potentially have for a file server at risk of ransomware.  Not even close!  This is just one option to work in conjunction with existing security and, as always, should be regularly revisited.

This was prepared in response to WannaCry as an additional layer of protection and I’ve meant to post it for a while and am just getting to it now.

First I will post the code then some explanation…

The Code:

####
# FSRM_POWERSHELL_FINAL.PS1
# Created By: Neal Britton
#
#
# Credit where credit is due: http://www.scconfigmgr.com/2017/03/21/protect-file-servers-from-ransomware-with-sccm-cicb/
# Credit where credit is due: https://fsrm.experiant.ca/
# Credit where credit is due: https://github.com/nexxai/CryptoBlocker
#
#
####

################################ USER CONFIGURATION ################################

# Names to use in FSRM
$fileGroupName = "CryptoBlockerGroup"
$fileTemplateName = "CryptoBlockerTemplate"
# set screening type to
# Active screening: Do not allow users to save unathorized files
$fileTemplateType = "Active"
# Passive screening: Allow users to save unauthorized files (use for monitoring)
#$fileTemplateType = "Passive"

################################ END USER CONFIGURATION ################################

################################ FUNCTIONS ################################

Function ConvertFrom-Json20
{
 # Deserializes JSON input into PowerShell object output
 Param (
 [Object] $obj
 )
 Add-Type -AssemblyName System.Web.Extensions
 $serializer = New-Object System.Web.Script.Serialization.JavaScriptSerializer
 return ,$serializer.DeserializeObject($obj)
}

Function New-CBArraySplit
{
 <# 
 Takes an array of file extensions and checks if they would make a string >1Kb, 
 if so, turns it into several arrays
 #>
 param(
 $Extensions
 )

$Extensions = $Extensions | Sort-Object -Unique

$workingArray = @()
 $WorkingArrayIndex = 1
 $LengthOfStringsInWorkingArray = 0

# Take the items from the input array and build up a 
 # temporary workingarray, tracking the length of the items in it and future commas
 $Extensions | ForEach-Object {

if (($LengthOfStringsInWorkingArray + 1 + $_.Length) -gt 1023) 
 { 
 # Adding this item to the working array (with +1 for a comma)
 # pushes the contents past the 1Kb limit
 # so output the workingArray
 [PSCustomObject]@{
 index = $WorkingArrayIndex
 FileGroupName = "$Script:FileGroupName$WorkingArrayIndex"
 array = $workingArray
 }
 
 # and reset the workingArray and counters
 $workingArray = @($_) # new workingArray with current Extension in it
 $LengthOfStringsInWorkingArray = $_.Length
 $WorkingArrayIndex++

}
 else #adding this item to the workingArray is fine
 {
 $workingArray += $_
 $LengthOfStringsInWorkingArray += (1 + $_.Length) #1 for imaginary joining comma
 }
 }

# The last / only workingArray won't have anything to push it past 1Kb
 # and trigger outputting it, so output that one as well
 [PSCustomObject]@{
 index = ($WorkingArrayIndex)
 FileGroupName = "$Script:FileGroupName$WorkingArrayIndex"
 array = $workingArray
 }
}

function CryptoBlocker
{

$SMTPServer = "server name"
 $SMTPFrom = "$env:COMPUTERNAME@domain name"
 $SMTPTo = "help desk email"
 $AdminEmail = "help desk email"

# Get all drives with shared folders, these drives will get FRSRM protection
 $DrivesContainingShares = @(Get-WmiObject Win32_Share | # all shares on this computer, filter:
 Where-Object { $_.Type -eq 0 } | # 0 = disk drives (not printers, IPC$, C$ Admin shares)
 Select-Object -ExpandProperty Path | # Shared folder path, e.g. "D:\UserFolders\"
 ForEach-Object { 
 ([System.IO.DirectoryInfo]$_).Root.Name # Extract the driveletter, as a string
 } | Sort-Object -Unique) # remove duplicates

if ($drivesContainingShares.Count -eq 0)
 {
 # No drives containing shares were found
 exit
 }

#Write-Host "`n####"
 #Write-Host "The following shares needing to be protected: $($drivesContainingShares -Join ",")"

# Identify Windows Server version, and install FSRM role
 $majorVer = [System.Environment]::OSVersion.Version.Major
 $minorVer = [System.Environment]::OSVersion.Version.Minor

#Write-Host "`n####"
 #Write-Host "Checking File Server Resource Manager.."

Import-Module ServerManager

if ($majorVer -eq 10)
 {
 $checkFSRM = Get-WindowsFeature -Name FS-Resource-Manager

if ($minorVer -ge 0 -and $checkFSRM.Installed -ne "True")
 {
 #Server 2016

$install = Install-WindowsFeature -Name FS-Resource-Manager -IncludeManagementTools
 if ($? -ne $True)
 {
 #Install of FSRM failed
 exit
 }
 }
 }
 elseif ($majorVer -eq 6)
 {
 $checkFSRM = Get-WindowsFeature -Name FS-Resource-Manager

if ($minorVer -ge 2 -and $checkFSRM.Installed -ne "True")
 {
 # Server 2012 or 2012 R2

$install = Install-WindowsFeature -Name FS-Resource-Manager -IncludeManagementTools
 if ($? -ne $True)
 {
 #Install of FSRM failed
 exit
 }
 }
 elseif ($minorVer -ge 1 -and $checkFSRM.Installed -ne "True")
 {
 # Server 2008 R2
 $install = Add-WindowsFeature FS-FileServer, FS-Resource-Manager
 if ($? -ne $True)
 {
 #Install of FSRM failed
 exit
 }
 
 }
 elseif ($checkFSRM.Installed -ne "True")
 {
 # Server 2008
 $install = &servermanagercmd -Install FS-FileServer FS-Resource-Manager
 if ($? -ne $True)
 {
 #Install of FSRM failed
 exit
 }
 }
 }
 else
 {
 # Assume Server 2003
 return
 }

# Download list of CryptoLocker file extensions
 $webClient = New-Object System.Net.WebClient
 $jsonStr = $webClient.DownloadString("https://fsrm.experiant.ca/api/v1/get")
 $monitoredExtensions = @(ConvertFrom-Json20 $jsonStr | ForEach-Object { $_.filters })

# Process SkipList.txt
 If (Test-Path .\SkipList.txt)
 {
 $Exclusions = Get-Content .\SkipList.txt | ForEach-Object { $_.Trim() }
 $monitoredExtensions = $monitoredExtensions | Where-Object { $Exclusions -notcontains $_ }

}
 Else 
 {
 #
 # Add one filescreen per line that you want to ignore
 #
 # For example, if *.doc files are being blocked by the list but you want 
 # to allow them, simply add a new line in this file that exactly matches 
 # the filescreen:
 #
 # *.doc
 #
 # The script will check this file every time it runs and remove these 
 # entries before applying the list to your FSRM implementation.
 #
 # In our environment we have a large number of false positives on *.one and WindowsApplication1.exe files
 #
 $emptyFile = @'
*.acc
*.one
WindowsApplication1.exe
'@
 Set-Content -Path .\SkipList.txt -Value $emptyFile
 }

# Split the $monitoredExtensions array into fileGroups of less than 4kb to allow processing by filescrn.exe
 $fileGroups = @(New-CBArraySplit $monitoredExtensions)
 $screenArgs = @()

# Perform these steps for each of the 1KB limit split fileGroups
 ForEach ($group in $fileGroups)
 {
 $FGExists = Get-FsrmFileGroup | Where-Object { $_.Name -eq "$($group.fileGroupName)" }
 if ($FGExists -gt $null)
 {
 Remove-FSRMFileGroup -name $($group.fileGroupName) -Confirm:$false | Out-Null
 }
 
 New-FSRMFileGroup -name $($group.fileGroupName) -IncludePattern $($group.array) -Description "Crypto Extension Detection" | Out-Null

$screenArgs += $($group.fileGroupName)
 }

# Create File Screen Template with Notification
 # Remove existing if found and create new
 $FSTExists = Get-FsrmFileScreenTemplate | Where-Object { $_.Name -eq "CryptoExtensions" }

if ($FSTExists -gt $null)
 {
 Remove-FsrmFileScreenTemplate -Name CryptoExtensions -Confirm:$false | Out-Null
 }

$screenArgList = [String[]]@($screenArgs)

$EmailNotification = New-FSRMAction -Type Email -Subject "Crypto File Activity Detected - $env:COMPUTERNAME" -Body "User [Source IO Owner] attempted to save [Source File Path] to [File Screen Path] on the [Server] server. This file is in violation of the [Violated File Group] file group. This file could be a marker for malware infection, and should be investigated immediately." -RunLimitInterval 30 -MailTo $SMTPTo
 $EventNotification = New-FSRMAction -Type Event -EventType Error -Body "User [Source IO Owner] attempted to save [Source File Path] to [File Screen Path] on the [Server] server. This file is in violation of the [Violated File Group] file group. This file could be a marker for malware infection, and should be investigated immediately." -RunLimitInterval 30

New-FsrmFileScreenTemplate -Name CryptoExtensions -Description "Known CryptoLocker File Extesions" -IncludeGroup($($screenArgList)) -Active:$true -Notification $EmailNotification,$EventNotification | Out-Null


# Create File Screens for every drive containing shares
 $drivesContainingShares | ForEach-Object {
 # Skip C:
 # All actual shares on our File Servers are on other drives. We only care about these.
 if ($_ -ne "C:\")
 {
 $drive = $_

# Remove File Screen is exists and create new
 $FSExists = Get-FsrmFileScreen | Where-Object { $_.Path -eq $drive }
 
 if ($FSExists -gt $null)
 {
 Remove-FsrmFileScreen -Path $_ -Confirm:$false | Out-Null
 }
 
 New-FSRMFileScreen -Path $_ -Active: $true -Description "Crypto Extension Monitoring" -Template CryptoExtensions -Notification $EmailNotification,$EventNotification | Out-Null
 }
 }

# Check for FSRM File Screen and report TRUE or FALSE for Configuration Item/Baseline Reporting
 $CryptoScreen = Get-FSRMFileScreen | Where-Object { $_.Description -eq "Crypto Extension Monitoring" }
 
 if ($CryptoScreen -gt $null)
 {
 $CryptoCICompliant = $true
 }
 else
 {
 $CryptoCICompliant = $false
 }
 Return $CryptoCICompliant
}

################################ END FUNCTIONS ################################

CryptoBlocker

 

The Explanation:

So, the above is just my implementation after finding similar items and expanding on them to meet my need.  It is not comprehensive and should definitely not stand alone.

Pros of this approach:

  • Automatic, just add a server to be protected to the collection this is deployed to
  • Can be regularly updated
  • The source list of ransomware files/extensions is somewhat trustworthy(?)
  • The protection groups are removed before they are rebuilt to ensure protection lists are up to date
  • Fast
  • No manual changes

Cons:

  • The source list is somewhat trustworthy
  • Is updated by manual submissions
  • Code has to be edited to exclude a certain file name or file extension (but only has to be done in one place)

 

The Implementation:

In your SCCM console in the Assets and Compliance work space, expand ‘Compliance Settings’ and in ‘Configuration Items’ create a new configuration item:

  1. In my example, give it a name of “CryptoBlocker FSRM Script” and hit next
  2. In Support Platforms, deselect all and the manually select your server platforms.  for me it was Server 2012, Server 2012 R2, and Server 2016
  3. Create a new setting
    1. Name = “CryptoBlocker FSRM Script”
    2. Setting Type: Script
    3. Data Type: Boolean
    4. Discovery Script (Powershell): Use above script
  4. Create a new compliance rule
    1. Name = “Crypto FSRM Protection
    2. Selected setting = The one we just created
    3. Set value returned by the specified script to equals true
    4. Check to enable “Remote noncompliance if this setting instance is not found”

Next, in Configuration Baselines, create a new baseline:

  1. Name = “Crypto File Block”
  2. Evaluation Conditions: Add the Configuration Item we created earlier

Deploy to a test collection first!  Pictures of the finished configuration item are as follows:

fsrm_ci1fsrm_ci2fsrm_ci3fsrm_ci4fsrm_ci5

Advertisements

SCCM – Enabling ‘Easy’ Local Login on Domain Computers During OSD Part 2 of 2: Applying The Info

Hello folks!

Link to Part 1: SCCM – Enabling ‘Easy’ Local Login on Domain Computers During OSD Part 1 of 2: Getting The Info

In my organization we have need for the occasional machine to be configured with local login for such things as display computers or book sign-out machines in a library.  Local login is easy, but having to revisit the machine after imaging to set it up is annoying and, frankly, a waste of time.  Unfortunately we hadn’t been successful or too interested in solving this.  Until now that is.

WARNING: This will be highly specific (yet generalized) to our implementation.  It is my hope that somehow you find some use out of this.

Part 1 will cover getting the info at the start of your task sequence while Part 2 will cover the application of the info to the machine.

Credit where credit is due: https://www.scconfigmgr.com/2014/06/06/prompt-for-ou-location-during-osd-with-powershell/ was where the start of this all came from.

Here we go…

The code (Set-LocalUserInfo.ps1)….

Import-Module -name .\policyfileeditor.psm1

$u2 = New-Object -COMObject Microsoft.SMS.TSEnvironment
$u = $u2.Value("LocalUserName")

$l2 = New-Object -COMObject Microsoft.SMS.TSEnvironment
$l = $l2.Value("LocalUserLocation")

# Set simple password of username x2 if username is short
if ($u -ne "long user name")
{
 $pass = $u + $u
}
else
{
 $pass = $u
}

$n = switch ($l)
{
 "A location" {"123"}
 "Another location" {"456"}
 "One more" {"789"}
}

# Website variable to be set
$website = "https://mywebsite.whatev?page=" + $n

# Create the local user
NET USER $u $pass /ADD /expires:never

# Set the above local user to not have an expiring password
Get-WmiObject Win32_UserAccount -filter "LocalAccount=True"|?{$_.name -eq $u} |Set-WmiInstance -Arguments @{PasswordExpires=$false}

# Create registry keys for local login
New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "DefaultUserName" -PropertyType "String" -Value $u
New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "DefaultPassword" -PropertyType "String" -Value $pass
New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "DefaultDomainName" -PropertyType "String" -Value '.\'
New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "AutoAdminLogon" -PropertyType "String" -Value '1'

Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "AutoLogonSID"

# If this computer is going to be for display, we don't want to show a website automatically when it logs on
# However, if the other alternative is a book lookup website, then we do want to fire up IE on login
if ($u -ne "display computer or whatever")
{
 New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -Name "IEXPLORE" -PropertyType "String" -Value '"C:\Program Files\Internet Explorer\iexplore.exe"'
}

# Set the power scheme to High Performance on logon to ensure the system stays on (which we want in this case)
New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -Name "SetMaximumPower" -PropertyType "String" -Value "C:\Windows\System32\powercfg.exe /SETACTIVE 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c"

# Specify local group policy file locations
$UserDir = "C:\windows\system32\GroupPolicy\User\registry.pol"
$MachineDir = "C:\windows\system32\GroupPolicy\Machine\registry.pol"

# Specify variables for setting screen saver (user setting)
$ScrnSvrPath = 'Software\Policies\Microsoft\Windows\Control Panel\Desktop';
$ScrnSvrName = 'SCRNSAVE.EXE'
$ScrnSvrData = 'scrnsave.scr'
$ScrnSvrType = 'String'
$ScrnSvrTimeoutName = 'ScreenSaveTimeOut'
$ScrnSvrTimeoutData = '120'

# Specify variables for disabling password prompt on computer wake (machine setting)
$RegPath = 'Software\Policies\Microsoft\Power\PowerSettings\0e796bdb-100d-47d6-a2d5-f7d2daa51f51'
$RegName = 'ACSettingIndex'
$RegName2 = 'DCSettingIndex'
$RegData = '0'
$RegType = 'DWord'

# Write settings to the local group policy
Set-PolicyFileEntry -Path $MachineDir -Key $RegPath -ValueName $RegName -Data $RegData -Type $RegType
Set-PolicyFileEntry -Path $MachineDir -Key $RegPath -ValueName $RegName2 -Data $RegData -Type $RegType

# Force a specific screen saver
# Sets screensaver to blank
# ONLY IF THE LOCAL USER IS *NOT* A DISPLAY USER
if ($u -ne "display")
{
 #Set screen saver to blank
 Set-PolicyFileEntry -Path $UserDir -Key $ScrnSvrPath -ValueName $ScrnSvrName -Data $ScrnSvrData -Type $ScrnSvrType
 
 #Set screen saver timeout to 2 minutes (120 seconds)
 Set-PolicyFileEntry -Path $UserDir -Key $ScrnSvrPath -ValueName $ScrnSvrTimeoutName -Data $ScrnSvrTimeoutData -Type $ScrnSvrType
}

# Prevent power plan turning off display after x minutes
# Sets value (in seconds) to 0 (disabled)
# ONLY IF THE LOCAL USER IS A DISPLAY USER
if ($u -eq "display")
{
 New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -Name "AC_TurnOffDisplayAfter" -PropertyType "String" -Value "C:\Windows\System32\powercfg.exe -setacvalueindex 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c 7516b95f-f776-4464-8c53-06167f40cc99 3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e 0"
 New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -Name "DC_TurnOffDisplayAfter" -PropertyType "String" -Value "C:\Windows\System32\powercfg.exe -setdcvalueindex 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c 7516b95f-f776-4464-8c53-06167f40cc99 3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e 0"
}

# Load ntuser.dat (default user)
reg load "HKEY_LOCAL_MACHINE\defuser" "C:\users\default\ntuser.dat"
# Create a new key to control local user website, close the handle, and trigger garbage collection
Set-ItemProperty -Path "HKLM:\defuser\SOFTWARE\Microsoft\Internet Explorer\Main" -Name "Start Page" -Value $website
[gc]::Collect()
#Unload ntuser.dat
reg unload "HKEY_LOCAL_MACHINE\defuser"

 

It is ugly, I know.  Truth be told, I’m hacking and slashing my way to a working solution just like I do in C# and after it works you don’t really want to mess with it.

Part of the code above is setting local group policy options that were, for one reason or another, difficult or otherwise impossible to set programmatically.  I found http://brandonpadgett.com/powershell/Local-gpo-powershell/ which pointed me to a module that would allow me to set local group policy objects using Powershell.  Highly recommended and without the module my script would have been sunk and half-done.

Save the script in the same location as the script from Part 1 and include the Powershell module accessible from the above link in there as well.  Oh, as with the SConfigMgr link above, include the ServiceUI.exe executable from MDT.  My folder ended up looking like this:

folder

Add a “Run Powershell Script” step to the end of your task sequence and configure:

settsstep

and

settsstepsettings

Presto!

So far it is working flawlessly in my Windows 8.1 deployment and am in the process of testing for Windows 10.

SCCM – Enabling ‘Easy’ Local Login on Domain Computers During OSD Part 1 of 2: Getting The Info

Hello folks!

Link to Part 2: SCCM – Enabling ‘Easy’ Local Login on Domain Computers During OSD Part 2 of 2: Applying The Info

In my organization we have need for the occasional machine to be configured with local login for such things as display computers or book sign-out machines in a library.  Local login is easy, but having to revisit the machine after imaging to set it up is annoying and, frankly, a waste of time.  Unfortunately we hadn’t been successful or too interested in solving this.  Until now that is.

WARNING: This will be highly specific (yet generalized) to our implementation.  It is my hope that somehow you find some use out of this.

Part 1 will cover getting the info at the start of your task sequence while Part 2 will cover the application of the info to the machine.

Credit where credit is due: https://www.scconfigmgr.com/2014/06/06/prompt-for-ou-location-during-osd-with-powershell/ was where the start of this all came from.

Here we go…

The code (Get-LocalUserInfo.ps1)…

param(
[parameter(Mandatory=$true)]
[string]$RBOptionFirst,
[parameter(Mandatory=$true)]
[string]$RBOptionSecond,
[parameter(Mandatory=$true)]
[string]$RBOptionThird
)
 
function Load-Form {
 $LocationList = @("A location", "Another location", "One more")
 $Form.Controls.AddRange(@($RBOption1, $RBOption2, $RBOption3, $ComboBox, $Button, $GBSystem, $GBLocation))
 $ComboBox.Items.AddRange($LocationList)
 $Form.Add_Shown({$Form.Activate()})
 [void]$Form.ShowDialog()
}
 
function Set-UserLocation {
 param(
 [parameter(Mandatory=$true)]
 $Location
 )
 if ($RBOption1.Checked -eq $true) {
 $UserName = "$($RBOptionFirst)"
 }
 if ($RBOption2.Checked -eq $true) {
 $UserName = "$($RBOptionSecond)"
 }
 if ($RBOption3.Checked -eq $true) {
 $UserName = "$($RBOptionThird)"
 }
 $TSEnvironment = New-Object -COMObject Microsoft.SMS.TSEnvironment 
 $TSEnvironment.Value("LocalUserName") = "$($UserName)"

$TSEnvironment2 = New-Object -COMObject Microsoft.SMS.TSEnvironment 
 $TSEnvironment2.Value("LocalUserLocation") = "$($Location)"

$Form.Close()
}
 
# Assemblies
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") 
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") 
 
# Form
$Form = New-Object System.Windows.Forms.Form 
$Form.Size = New-Object System.Drawing.Size(260,220) 
$Form.MinimumSize = New-Object System.Drawing.Size(360,220)
$Form.MaximumSize = New-Object System.Drawing.Size(360,220)
$Form.SizeGripStyle = "Hide"
$Form.StartPosition = "CenterScreen"
$Form.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($PSHome + "\powershell.exe")
$Form.Text = "Choose user details"
$Form.ControlBox = $false
$Form.TopMost = $true
 
# Group boxes
$GBSystem = New-Object System.Windows.Forms.GroupBox
$GBSystem.Location = New-Object System.Drawing.Size(10,10)
$GBSystem.Size = New-Object System.Drawing.Size(320,60)
$GBSystem.Text = "Select user"
$GBLocation = New-Object System.Windows.Forms.GroupBox
$GBLocation.Location = New-Object System.Drawing.Size(10,80)
$GBLocation.Size = New-Object System.Drawing.Size(220,60)
$GBLocation.Text = "Select location"
 
# Radio buttons
$RBOption1 = New-Object System.Windows.Forms.RadioButton
$RBOption1.Location = New-Object System.Drawing.Size(20,33)
$RBOption1.Size = New-Object System.Drawing.Size(100,20)
$RBOption1.Text = "$($RBOptionFirst)"
$RBOption1.Add_MouseClick({$ComboBox.Enabled = $true})
$RBOption2 = New-Object System.Windows.Forms.RadioButton
$RBOption2.Location = New-Object System.Drawing.Size(120,33)
$RBOption2.Size = New-Object System.Drawing.Size(100,20)
$RBOption2.Text = "$($RBOptionSecond)"
$RBOption2.Add_MouseClick({$ComboBox.Enabled = $true})
$RBOption3 = New-Object System.Windows.Forms.RadioButton
$RBOption3.Location = New-Object System.Drawing.Size(220,33)
$RBOption3.Size = New-Object System.Drawing.Size(100,20)
$RBOption3.Text = "$($RBOptionThird)"
$RBOption3.Add_MouseClick({$ComboBox.Enabled = $true})
 
# Combo Boxes
$ComboBox = New-Object System.Windows.Forms.ComboBox
$ComboBox.Location = New-Object System.Drawing.Size(20,105)
$ComboBox.Size = New-Object System.Drawing.Size(200,30)
$ComboBox.DropDownStyle = "DropDownList"
$ComboBox.Add_SelectedValueChanged({$Button.Enabled = $true})
$ComboBox.Enabled = $false
 
# Buttons
$Button = New-Object System.Windows.Forms.Button
$Button.Location = New-Object System.Drawing.Size(140,145)
$Button.Size = New-Object System.Drawing.Size(80,25)
$Button.Text = "OK"
$Button.Enabled = $false
$Button.Add_Click({Set-UserLocation -Location $ComboBox.SelectedItem.ToString()})
 
# Load Form
Load-Form

 

As the above link mentions, using this script requires that Microsoft .NET (WinPE-NetFx) and Windows PowerShell (WinPE-PowerShell) be added to your boot wim and (re)ristributed to your distribution points.

Save the above script to your package source location and create a package for it.  In my case, the name is ‘Set Local user Info’ and includes some other scripts and components which will be discussed in Part 2.

Create a “Run Command Line” task sequence step early in the TS list:

TSStep

And configure it as follows:

TSStep2

If all goes well, the following will appear when you begin your task sequence…

TSStep3

Selecting either of the three radio buttons allows the location drop down list to appear.  Select the name/type of user you passed via the command line and then pick the location (why this is offered is covered in Part 2).

Stay tuned for Part 2!

SCCM – Deploying .NET Framework 4.6.2 Fails With Error 16389

Hello everyone,

In preparation for some upcoming application deployments that rely on the latest .NET Framework I did a test deployment to a small (50-ish) collection of Windows 8.1 x64 laptops.

My .NET 4.6.2 setup is fairly simple: it’s in SCCM as an application using the full executable and my full install looks like this:

NDP462-KB3151800-x86-x64-AllOS-ENU.exe /q /norestart

Almost immediately I had a failure and they kept rolling in, all failing with a 16389.

Luckily, the fix was fairly simple…

After a quick look online I went into my application and in the ‘Program’ tab I had to click to enable the “Run installation and uninstall as 32-bit process on 64-bit clients.”.  D’oh!

There it is:

net4.6.2

Once that was clicked and redistributed out to the distribution points (just to be safe) the angry red failures have all disappeared!

SCCM – Set Windows 10 Desktop Background To Solid Colour (For All Users) During OSD

Hey everyone,

I’d like to preface this blog post by saying that this was perhaps the one setting that took the longest to figure out as there wasn’t much of anything about it online at the time and what was found just didn’t seem to work right.  Thankfully, after a whole bunch of trial and error and many reimagings of my VM the answer was finally at hand.

This script, as with most I have, runs during my image creation and during my actual OSD task sequence.  It is definitely unnecessary but after trying so hard to figure it out you just want to know that it is applying.

Anyway, my first step was to create a 1920×1080 img0.jpg file of the solid colour I wanted.  The file is img0.jpg because that is what the default background in Windows 10 is called.  I chose one of the lighter-blues that Windows let you choose from when setting a background.  This has traditionally been our background as it isn’t hard on the eyes and we haven’t bothered to change it.  Combined with ‘our colour’ being an off-lime green this is the better choice.

Second step is to create my loadSolidColourBackground.cmd script that contains the followinging…

takeown /f c:\windows\WEB\wallpaper\Windows\img0.jpg
takeown /f C:\Windows\Web\4K\Wallpaper\Windows\*.*
icacls c:\windows\WEB\wallpaper\Windows\img0.jpg /Grant System:(F)
icacls C:\Windows\Web\4K\Wallpaper\Windows\*.* /Grant System:(F)

del /q C:\Windows\Web\4K\Wallpaper\Windows\*.*
xcopy img0.jpg C:\Windows\Web\Wallpaper\Windows\ /Q /Y

Wrap all of these in a package in SCCM and you’re ready!

Add a “Run Command Line” step in your task sequence(s) pointing to your new package and run “cmd.exe /c loadSolidColourBackground.cmd”.

SCCM – Remove Built-In OneDrive From Windows 10 During OSD

Hi everyone,

One of the “features” on Windows 10 is, whether you’re going to use it or not, OneDrive is installed without asking for it (even if the first application install of your Task Sequence is Office and OneDrive for Business (or you bake it in during image creation)).

Anyway, it is pretty easy to get rid of but it involves editing the default user registry.  I made a blog post before this describing my basic method to do this: SCCM – Edit The Default User Registry

What I have is a “Install Command Line” step in my Task Sequence that runs “cmd.exe /c removeOneDrive.cmd”.  This script in turn gets made in SCCM as a package.  This is what is in that script…

reg load "hku\Default" "C:\Users\Default\NTUSER.DAT" 
reg delete HKU\default\software\Microsoft\Windows\CurrentVersion\Run /v OneDriveSetup /f
reg unload "hku\Default"

The majority of my scripts I run in both the image creation task sequence and my deployment task sequence, mainly so I am sure that at some point it was killed.

SCCM – Edit The Default User Registry

Hey everyone,

Hoping to do a few posts today but none of them are possible without a little fun tinkering with the registry.  Not just any registry but the default user registry.  **Inset obligatory warning about tinkering with the registry**

I do the registry editing via command line in .bat/.cmd files.

The basic format:

reg load "hku\Default" "C:\Users\Default\NTUSER.DAT" 
<registry editing>
reg unload "hku\Default"

And that is about it!  Until next time…

SCCM – Remove “Contact Support” From Windows 10 1607 During OSD

EDIT 03/22/2017:  As blogged HERE by Michael Niehaus it is possible to remove this pesky program via powershell with the following:

Get-WindowsCapability -online | ? {$_.Name -like '*ContactSupport*'} | Remove-WindowsCapability –online

 

Hi everyone,

It has been quite a while since my last blog post and a lot has been going on.  I’m going to slowly start posting new items as I find the time to show some of the things I’ve created (or found) for SCCM as I have been working towards creating a master Windows 10 image.

In this post, I will be talking about removing the questionable “Contact Support” app that Microsoft for some reason saw fit to include in the Enterprise and Education editions of Windows 10.

I was able to remove it with a simple powershell script running in a normal task sequence in a “Run Command Line” step.  I am opting to simply rename the folder it lies in instead of outright removing it because hey, who knows, one day it might be needed.

The script:

powershell.exe -executionpolicy bypass Rename-Item C:\Windows\SystemApps\ContactSupport_cw5n1h2txyewy BLOCKED_ContactSupport_cw5n1h2txyewy

That’s it!  This is what it fully looks like in the task sequence and the result:

sccm_cmdremovecontactsupportbyebyecontactsupport

Until next time…

SCCM – Some Experiences with Error 0x8004005

Howdy folks and welcome back!

In addition to Service Manager, I also do a lot of work with Configuration Manager (or SCCM if you prefer).  Actually, about 90-99% of my System Center time is directed at SCCM, so I have had a fair amount of experience with it in our environment.

One of the more vexing errors you will find in SCCM during OSD is error 0x8004005 which if anything is a generic error that can be triggered by a number of things.  Instead of doing a Case 1, Case 2, etc way of going through the scenarios, these are the top things to look for when troubleshooting this issue when it pops up (again, during OSD (Operating system Deployment)).

  • Ensure the data/time of the system is correct
  • Ensure the BIOS is up to date
  • Check your network account and ensure if has security access in Software Library -> Operating Systems -> Operating System Images
    • For good measure, double-check to ensure the username/password is correct anyway
  • Ensure the hard drive is in good working order (chkdsk /r and drive diagnostics are your friends)

Lastly: IT COULD JUST BE THE TASK SEQUENCE!  We have literally solved a 0x8004005 issue in our environment by deleting the existing task sequence and recreating an exact duplicate in its place and redeploying it.  If all else fails, try it.  You might just want to bang your head on a desk if it works because that is all there is left to do.

DIY Service Manager Analyst Portal – Part 6 – So What Does It All Look Like?

Hey folks!

It has been a while since I posted.  OK, more than a while.

In previous parts of the Analyst Portal series I have posted the code that drives what I can see on my Analyst Portal but…what good is that if you can’t see it unless you implement the code?

This is what the default.aspx website looks like…

defaultaspx

This is what the details.aspx website looks like, using generic data…

detailsaspx

This is what the edit.aspx website looks like…

editaspx

Finally, this is what the serial.aspx website looks like, both blank and after a serial number has been typed and submitted…

serialaspx_1serialaspx_2

It looks fairly…bland…but on a mobile device (such as my S5) it is crisp and clear and, unless someone has included a link in the description field, all fits width-wise on the screen and easily scroll-able and usable.

There are vendors out there that will offer you an Analyst Portal, but my sticking point is having to pay for something that can be done in-house.  This is just a basic portal in its current incarnation but could be built upon.  Everything needed is in the SDK.

Cheers.