Clean Windows C Volume

Have you ever ran into a sluggish performance on a Windows machine that is being shared by many [remote] users? Chances are that each user has lots of junk files that would be saved in certain important volumes, such as C:\ drive. Thus, I’ve put together these spaghetti lines to automate maintenance of these machines.

# cleanCVolume.ps1

## Variables Declaration ####
$directoriesToPurge=@(
    "$env:SystemDrive\Windows\Prefetch\", # $prefetchData
    "$env:SystemDrive\Windows\Logs\CBS\", # $cbs
    "$env:SystemDrive\swtools\", # $swtools
    "$env:SystemDrive\drivers\", # $drivers
    "$env:SystemDrive\swsetup\", # $swsetup
    "$env:SystemDrive\Windows\SoftwareDistribution\Download\", #$softwareDistribution
    'C:\windows\Temp',
    'C:\Temp',
    'C:\ProgramData\Microsoft\Windows\WER\ReportArchive',
    'C:\ProgramData\Microsoft\Windows\WER\ReportQueue',
    'C:\ServiceProfiles\LocalService\AppData\Local\Temp'
)


function purgeDirectory($path='c:\temp'){     
    function confirmation($content,$testValue="I confirm",$maxAttempts=3){
            $confirmed=$false;
            $attempts=0;        
            $content|write-host
            write-host "Please review this content for accuracy.`r`n"
            while ($attempts -le $maxAttempts){
                if($attempts++ -ge $maxAttempts){
                    write-host "A maximum number of attempts have reached. No confirmations received!`r`n"
                    break;
                    }
                $userInput = Read-Host -Prompt "Please type in this value => $testValue <= to confirm. Input CANCEL to skip this item.";
                if ($userInput.ToLower() -eq $testValue.ToLower()){
                    $confirmed=$true;
                    write-host "Confirmed!`r`n";
                    break;                
                }elseif($userInput -like 'cancel'){
                    write-host 'Cancel command received.'
                    $confirmed=$false
                    break
                }else{
                    cls;
                    $content|write-host
                    write-host "Attempt number $attempts of $maxAttempts`: $userInput does not match $testValue. Try again or Input CANCEL to skip this item`r`n"
                    }
                }
            return $confirmed;
        }
    write-host "Granting Administrators full access to $path..."
    try{
        # $null=takeown /F $path /A /R /D Y
        $null=icacls $path /Grant Administrators:F /inheritance:e /T
    }catch{
        write-warning $_
        continue
    }
    $emptyDirectory="C:\emptyDirectory"
    md -Force $emptyDirectory
    Remove-Item "$emptyDirectory`\*" -force -recurse -ErrorAction Continue
    $confirmed=confirmation "This will delete all files and folders from $path"
    if ($confirmed){
        write-host "Now purging ALL files and folders inside $path..." -foregroundcolor yellow
        $null=robocopy $emptyDirectory $path /mir /R:0 /W:0 /NP
        # rmdir -Force $path
    }else{
        write-host "Purging has been cancelled for $path"
        }
}
function cleanUserProfiles{
    $profiles=(get-childitem c:\users -Directory -EA Ignore).Name
    if($profiles){
        Foreach($profile in $profiles){            
            $tempPath = "C:\Users\$profile\AppData\Local\Temp"
            $downloadPath = "C:\Users\$profile\Downloads"
            purgeDirectory $tempPath
            Write-host "$tempPath cleared."
            purgeDirectory $downloadPath
            Write-host "$downloadPath cleared."
        }
    }
}

function cleanWindows{
    foreach($directoryToPurge in $directoriesToPurge){
        purgeDirectory $directoryToPurge
    }
    write-host "Clearing all superseded versions of every component in the component store...."
    # This also removes any backup components needed for uninstallation of service packs already installed
    Dism.exe /online /Cleanup-Image /startcomponentcleanup /resetbase
}

cleanUserProfiles
cleanWindows

PowerShell: Change Google Chrome Cache Directory

$newCacheDirectory="D:\chromeCache\$env:username"
function changeChromeCacheDirectory{
    param(
      $newCacheDirectory="D:\chromeCache\$env:username",
      $defaultCacheDirectory="C:\Users\$env:username\AppData\Local\Google\Chrome\User Data\Default\Cache"
    )
    try{
        Stop-Process -Name chrome
        mkdir $newCacheDirectory
        rmdir $defaultCacheDirectory -force -Recurse
        New-Item -ItemType Junction -Path $defaultCacheDirectory -Target $newCacheDirectory
        write-host "Chrome cache directory has been changed`r`nFrom: $defaultCacheDirectory`r`nTo: $newCacheDirectory" -ForegroundColor Green
        return $true
    }catch{
        write-warning $_
        return $false
    }
}

changeChromeCacheDirectory $newCacheDirectory

The Process of Adding a New Hyper-V Server Into a Cluster and the Associated Virtual Machine Manager (VMM)

These are the steps:

  1. Install Windows
    Windows 2019 Data Center Edition is the standard as of May 20, 2022
    Certain hardware may require slipstreamed ISO’s to load RAID drivers so that the installation wizard would recognize the underlying hardware
    Install Windows with GUI. Although, headless Windows would still work, our Admins prefer to have GUI, boo
  2. Setup Network connectivity
    Obtain a static IP for the server
    Assign an IP for new server using
    Setup Interface with static IP, Gateway, and DNS
  3. Ensure that machine is accessible to VMM on these ports
    Ports required by SCVMM:
    TCP/22
    TCP/80
    TCP/135
    TCP/139
    TCP/445
    TCP/443
    TCP/623
    TCP/1433
    TCP/5985
    TCP/5986
    TCP/8530
    TCP/8531
    TCP/8100
    TCP/8101
    TCP/8102
    TCP/49152 to TCP/65535
    Ports required by Prometheus Windows Exporter:
    TCP/9182
  4. Enable RDP
    This feature would automatically be installed when a machine joins the domain. However, we’re ensuring that the hardware and OS would not go into a recovery loop due to driver issues. Hence, we would be patching Windows and attempting enable its Hyper-V features as a precursor to joining the machine to to domain, only if the machine is stable after these functions are added.
  5. Disable Startup repair
    Run this command: bcdedit /set {current} recoveryenabled no
  6. Update Windows
    There’s a PowerShell method to updating Windows directly from Microsoft
    Alternative, the equivalent GUI method would suffice
  7. Install Hyper-V Features: Install-WindowsFeature -Name Hyper-V -IncludeManagementTools -Restart
    Be advised that Windows will reboot after this command
    If there were any driver conflicts, Windows would go into a recovery loop. Hopefully, that doesn’t happen to the machine you’re preparing
  8. Join Domain
  9. Run these PowerShell Command as Administrator
    # Install Chocolatey
    if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))}

    # Defining $ENV:ChocotaleyInstall so that it would be called by refreshenv
    $ENV:ChocolateyInstall = Convert-Path "$((Get-Command choco).Path)\..\.."
    Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
    Update-SessionEnvironment
    # Add Private Repo
    $privateRepoName='kimconnectrepo'
    $privateRepoUrl='https://choco.kimconnect.com/chocolatey'
    $priority=1
    $privateRepo="'$privateRepoName' -s '$privateRepoUrl' --priority=$priority"
    choco source add -n=$privateRepo

  10. Uninstall Windows Defender
    Run this command: Remove-WindowsFeature Windows-Defender
  11. Install Enterprise Antivirus
  12. Install Prometheus Windows Exporter
    Use Chocolatey to install Prometheus Windows_Exporter
    choco install prometheus-windows-exporter.install -y --ignore-checksums
    $null=& sc.exe failure $serviceName reset= 30 actions= restart/100000/restart/100000/""/300000
    Set-Service -Name $serviceName -StartupType 'Automatic'
    Enabling Collectors for a Hyper-V Server
    $serviceName='Windows_Exporter'
    $enabledCollectors='os,cpu,cs,logical_disk,net,tcp,hyperv,service,textfile'
    $switches=" --log.format logger:eventlog?name=$serviceName --collectors.enabled $enabledCollectors"
    function modifyService{
    param(
    $serviceName,
    $switches
    )
    $wmiService=Get-WmiObject win32_service| ?{$_.Name -like "*$serviceName*"}
    $exePath=[regex]::match($wmiService.PathName,'\"(.*)\"').groups[1]
    $binarySwitches='\"' + $exePath + '\"' + $switches
    sc.exe config $serviceName binpath= $binarySwitches
    sc.exe qc windows_exporter
    restart-service $serviceName
    }
    # Local execution
    modifyService $serviceName $switches
  13. Install Failover Clustering Features
    # After restart, install Failover clustering features
    Install-WindowsFeature Failover-Clustering -IncludeManagementTools; Install-WindowsFeature RSAT-Clustering-MGMT,RSAT-Clustering-PowerShell,Multipath-IO
  14. Setup NIC Teaming (if appropriate)
    # Check Teaming setup
    Get-NetLbfoTeam

    # Initialize Teaming, if required
    $teamName='Team1'
    $vlanId=100
    Add-NetLbfoTeamNIC -Team $teamName -VlanID $vlanId

    # Set Teaming mode, if required
    $teamName='Team1'
    $teamMode='LACP'
    $lbAlgorithm='Dynamic'
    Set-NetLbfoTeam -Name $teamName -TeamingMode $teamMode -LoadBalancingAlgorithm $lbAlgorithm

    # Add Team members, if required
    $teamName='Team1'
    $nicTeamMembers='NIC1','NIC2'
    $nicTeamMembers|%{Add-NetLbfoTeamMember -Name $_ -Team $teamName}

    # Check Teaming setup, if required
    Get-NetLbfoTeam

    # Check NIC names
    Get-NetAdapter

  15. Enable WinRM: Enable-PSRemoting -Force
  16. Enable CredSSP: Enable-WSManCredSSP -Role Server -Force
  17. Create New Virtual Switch(es)
    $switchName='External-Connection' # change this label to reflect the actual vSwitch name to be added
    $adapterName='TEAM1' # change this value to reflect the correct interface
    $vlanId=101 # change this to the correct VLAN
    function addVirtualSwitch($switchName,$adapterName,$vlanId){
    New-VMSwitch -name $switchName -NetAdapterName $adapterName -AllowManagementOS $true
    if($vlanId){
    Get-VMNetworkAdapter -SwitchName $switchName -ManagementOS|Set-VMNetworkAdapterVlan -Access -VlanId $vlanId
    }
    }
    addVirtualSwitch $switchName $adapterName $vlanId
  18. Include appropriate Admins
    # Example for HQ
    $admins=@(
    "$env:USERDOMAIN\$($env:computername)$",
    "$env:USERDOMAIN\service-hv",
    "$env:USERDOMAIN\service-vmm"
    )
    Add-localgroupmember -group Administrators -member $admins
  19. Join Node to Cluster
    # Example
    $clusterName='hyperv-cluster'
    Add-ClusterNode -Name $env:computername -Cluster $clusterName
  20. Install VMM Agent
    The easy method:
    # Install VMM Agent
    $vmmServer='hq-vmm01' # change this value to reflect the correct VMM node
    $version='10.19.2591.0' # change to value to reflect the latest expected version
    $agentMsiFile="\\$vmmServer\C$\Program Files\Microsoft System Center\Virtual Machine Manager\agents\amd64\$version\vmmAgent.msi"

    # Installing VMM Agent using its MSI File
    $file=gi $agentMsiFile
    $DataStamp = get-date -Format yyyyMMddTHHmmss
    $logFile ="C:\" + '{0}-{1}.log' -f $file.name,$DataStamp
    $MSIArguments = @(
    "/i"
    ('"{0}"' -f $file.fullname)
    "/qn"
    "/norestart"
    "/L*v"
    $logFile
    )
    Start-Process "msiexec.exe" -ArgumentList $MSIArguments -Wait -NoNewWindow
  21. Add This New Hyper-V Node to VMM
    Access VMM as Administrator > Navigate to Fabric > select the appropriate cluster > right-click the newly introduced node > Associate with Cluster > Done

PowerShell: Check a List of Windows Computers for Online & Offline Statuses

# checkOnlineComputers.ps1

# Set a list of computers in a text file
$computerListFile='C:\Temp\computerlist.txt'

# Read the computer list and run SMB tests against each one
$content=get-content $computerListFile
$results=[hashtable]@{}
foreach ($line in $content){
  $smbReach=test-path "\\$($line.trim())\C$\Windows"
  write-host "$line`: $smbReach"
  #pause
  $results+=@{$line=$smbReach}
  }

# Display the results
$results.GetEnumerator() | ?{!$_.value} | %{
    $message = '{0}: {1}' -f $_.key, $_.value
    Write-Output $message
}  

PowerShell: Add Local User as an Administrator on All Servers in Domain


# addLocalAccountOnAllServers.ps1
# Feature: using only legacy commands for maximum compatibility

# Set variables
$newUsername='backupAdmin'
$newUserPass='VERYCOMPLEXPASSWORD'
$newUserFullName="Local System Admin"
$newUserDesc="Standardized local admin user"
$newUserGroup="Administrators"

function addLocalAccount{
    param(
        $servers=$env:computername,
        $newUsername='backupAdmin',
        $newUserPass='COMPLEXPASSWORDHERE',
        $newUserFullName="Systems Admin",
        $newUserDesc="Standardized local admin user",
        $newUserGroup="Administrators"
    )
    $results=@()
    $psSessionOptions=New-PSSessionOption -SkipCNCheck -OpenTimeOut 60
    foreach ($server in $servers){
        $pssession=new-pssession $server -SessionOption $psSessionOptions -EA Ignore
        $progress=if($pssession.State -eq 'Opened'){
                Invoke-command -session $pssession -ScriptBlock {
                    param($newUsername,$newUserPass,$newUserFullName,$newUserDesc,$newUserGroup)                            
                    # Check whether username exists and proceed accordingly
                    $usernameExists=$(net user $newUsername)[0] -match $newUsername
                    try{
                        if(!$usernameExists){
                            # Using legacy commands for maximum compatibility
                            $null=NET USER $newUsername $newUserPass /fullname:"$newUserFullName" /comment:"$newUserDesc" /Active:Yes /ADD /Y
                            write-host "$newUserName has been created on $env:computername successfully"
                        }else{
                            # if user exists, ensure that its password is matching the intended value
                            $null=invoke-expression "net user $newUsername $newUserPass" 2>&1
                            write-host "$newUserName exists on $env:computername and its password has been reset"
                        }
                        $isMembershipValid=$(net localgroup $newUserGroup) -match $newUsername
                        if(!$isMembershipValid){
                            $null=invoke-expression "NET LOCALGROUP $newUserGroup $newUsername /ADD /Y" 2>&1
                            write-host "$newUserName has been added to group $newUserGroup on $env:computername successfully"
                        }else{
                            write-host "$newUserName is already a member of group $newUserGroup on $env:computername"
                        }
                        $null=Net user $newUsername /active:yes
                    }catch{
                        write-warning $_
                        return $false
                    }                    
                    # Validation
                    $userEnabled=$(net user $newUsername)[5] -match 'Yes'
                    return $userEnabled
                  
                    # These lines only work in PowerShell 5.1+; hence, they are skipped
                    # New-LocalUser $newUsername -Password $newUserPass -FullName $newUserFullName -Description $newUserDesc
                    # Add-LocalGroupMember -Group $newUserGroup -Member $newUsername
                } -Args $newUsername,$newUserPass,$newUserFullName,$newUserDesc,$newUserGroup
                remove-pssession $pssession
            }else{
                write-warning "$env:computername is unable to connect to $server via WinRM"
                $null
            }
        $result=[pscustomobject]@{
            'computername'=$server
            'localUserExists'=$progress
            }
        write-host $result
        $results+=$result
    }
    return $results
}

# Get all servers, excluding domain controllers
$memberServers=Get-ADComputer -Filter 'operatingsystem -like "*server*" -and enabled -eq "true" -and primarygroupid -ne "516"' -Properties Name,Operatingsystem,OperatingSystemVersion,IPv4Address | Sort-Object -Property Operatingsystem | Select-Object -Property Name,Operatingsystem,OperatingSystemVersion,IPv4Address
$servers=$memberServers.Name

$results=addLocalAccount $servers $newUsername $newUserPass $newUserFullName $newUserDesc $newUserGroup

write-host $results

How To Modify a Windows Service

This is an example of how to modify a service named Windows_Exporter:

# Enabling Collectors for a Hyper-V Server
$serviceName='Windows_Exporter'
$enabledCollectors='os,cpu,cs,logical_disk,net,tcp,hyperv,service,textfile'
$switches=" --log.format logger:eventlog?name=$serviceName --collectors.enabled $enabledCollectors"

function modifyService{
	param(
		$serviceName,
		$switches
	)	
	$wmiService=Get-WmiObject win32_service| ?{$_.Name -like "*$serviceName*"}
	$exePath=[regex]::match($wmiService.PathName,'\"(.*)\"').groups[1]
	$binarySwitches='\"' + $exePath + '\"' + $switches
	sc.exe config $serviceName binpath= $binarySwitches
	sc.exe qc windows_exporter
	restart-service $serviceName
}

# Local execution
modifyService $serviceName $switches
# Remote execution
$computerNames=@(
	'HyperV001',
	'HyperV002',
	'HyperV003',
	'HyperV004'
)
foreach($computer in $computernames){
	write-host "Executing function on $computer..."
	invoke-command -computername $computer -scriptblock {
			param($modifyService,$servicename,$switches)
			[ScriptBlock]::Create($modifyService).Invoke($servicename,$switches)
		} -Args ${function:modifyService},$servicename,$switches
}

How To Modify Collectors of Windows Exporter

Update: here’s a generalized version of this function that can be applied to other services

The follow cmdlets assumes that Prometheus Windows Exporter has already been installed. This is how to modify an existing instance:

# Example: Enabling Collectors for a Hyper-V Server
$serviceName='Windows_Exporter'
$enabledCollectors='os,cpu,cs,logical_disk,net,tcp,hyperv,service,textfile'
$wmiService=Get-WmiObject win32_service| ?{$_.Name -like "*$serviceName*"}
$exePath=[regex]::match($wmiService.PathName,'\"(.*)\"').groups[1]
$binaryPath = '\"' + $exePath + '\"' + " --log.format logger:eventlog?name=windows_exporter --collectors.enabled $enabledCollectors"
sc.exe config $serviceName binpath= $binaryPath
sc.exe qc windows_exporter
restart-service $serviceName

How To Create a Windows Scheduled Task to Call a Program or Script

Example on How To Call a Program:
  1. Set Action = Start a Program
  2. Set Program/Script = C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe (must specify the full path of PowerShell in Windows)
  3. Set Arguments = -Command "& 'C:\Windows\system32\ping.exe' -n 1 google.com"
  4. Set Start-in = C:\Windows\system32 (or where ever the executable resides)
  5. If program would require Administrator context, don’t forget to set task to run with elevated permissions

Example on How To Call a PowerShell Script:
  1. Set Action = Start a Program
  2. Set Program/Script = C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe (must specify the full path of PowerShell in Windows)
  3. Set Arguments = -Command "& 'C:\Scripts\deleteLogsOlderThanXDays.ps1'"
  4. Set Start-in = C:\Scripts (or where ever the script resides)
  5. If program would require Administrator context, don’t forget to set task to run with elevated permissions

PowerShell: Delete Files Older Than 30 Days

Have you ever run into C:\ volumes reaching critical thresholds because certain applications or users filling up system drive, causing Windoze to run out of disk space and perform sluggishly? Well, you’re in luck:

# deleteLogsOlderThanXDays.ps1
# Version 0.0.1

# User defined variables
$logDirectories='C:\Temp'
$deleteLogsOlderThanDays=30
$testOnly=$false
$logFile='C:\purgedFiles.txt'
$renameLogGreaterThanMb=5

# Execute deletion
# Safety check
$hardStopLocations=@(
	'C:\Windows',
	'C:\ProgramData',
	'C:\Users',
	'C:\Program Files (x86)',
	'C:\Program Files'
	)
$ErrorActionPreference='ignore'
$today=Get-Date
$deletionMarker=$today.AddDays(-$deleteLogsOlderThanDays)
$deletedFiles="`r`nJob started $($today.DateTime.ToString())`r`n--------------------------------------------`r`n"
foreach($directory in $logDirectories){
	$isSafe=.{
		$parent=if(test-path $directory){
				try{
					(get-item $directory -EA Ignore).parent.FullName
				}catch{
                    $null
                }
			}else{
				write-warning "$directory cannot be parsed."
				return $null
			}
		if($parent -eq $null){
				write-warning "$directory is invalid"
				return $false
		}elseif($parent -in $hardStopLocations){
			write-warning "$directory is not a safe location to manipulate files."
			return $false
		}else{
			return $true
		}
	}
	if($isSafe){
		if($testOnly){
			Get-ChildItem $directory -Recurse -EA Ignore|?{ $_.LastWriteTime -lt $deletionMarker }|%{write-host "Marked for deletion: $_"}
		}else{			
			$itemsToDelete=Get-ChildItem $directory -Recurse -EA Ignore|?{$_.LastWriteTime -lt $deletionMarker}
			if($itemsToDelete){
				foreach ($item in $itemsToDelete){
					try{
						Remove-Item $item.fullname -ea stop
						write-host "Deleted: $item"
						$deletedFiles+="Deleted: $($item.Fullname) with lastWriteTime of $($item.LastWriteTime.toString())`r`n"
					}catch{
						write-warning $_
					}
				}		
			}else{
                $comment="There are no item(s) to delete in $directory`r`n"
				write-host $comment
                $deletedFiles+=$comment
			}
		}
	}
}
$deletedFiles+="--------------------------------------------`r`nJob ended $((get-date).DateTime.ToString())"
if(test-path $logFile){
    $logFileInfo=get-item $logFile
    if($logFileInfo.length/1MB -gt $renameLogGreaterThanMb){
        $timeZoneName=[System.TimeZoneInfo]::Local.StandardName
        $timeStampFileName=[System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId([datetime]::UtcNow,$timeZoneName).ToString('yyyy-MM-dd_HH-mm-ss')+'_'+[regex]::replace($timeZoneName,'([A-Z])\w+\s*', '$1')
        $abbreviatedZoneName=if($timeZoneName -match ' '){[regex]::replace($timeZoneName,'([A-Z])\w+\s*', '$1')}else{$timeZoneName}
        $timeStampFormat="yyyy-MM-dd_HH-mm-ss_$abbreviatedZoneName"
        $timeStampFileName=[System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId([datetime]::UtcNow,$timeZoneName).ToString($timeStampFormat)
        $newName=join-path $logFileInfo.DirectoryName $($logFileInfo.BaseName+"_$timeStampFileName"+$logFileInfo.Extension)
        rename-item $logFile $newName
    }
}
Add-Content $logFile $deletedFiles
write-host "Log has been written to $logFile"

How to Add New Lookup Domains into Windows DNS Suffixes

Error message:

[server005.intranet.kimconnect.com]: PS E:\Users\kimconnect\Documents> add-localgroupmember -Group 'remote desktop users' -Member 'DMZ/testUser'
add-localgroupmember : Principal DMZ/testUser was not found.
+ CategoryInfo : ObjectNotFound: (WBMD/SVasmatkar:String) [Add-LocalGroupMember], PrincipalNotFoundException
+ FullyQualifiedErrorId : PrincipalNotFound,Microsoft.PowerShell.Commands.AddLocalGroupMemberCommand

# Resolution: Add Entries to DNS Suffixes

$addDomains='dmz.kimconnect.com'
$currentList=(Get-DnsClientGlobalSetting).SuffixSearchList
$newList=@($addDomains)
$newList+=$currentList
Set-DnsClientGlobalSetting -SuffixSearchList $newList

# Use legacy command to add user into local group (in case Windoze OS is dinosaur)

net localgroup 'remote desktop users' DMZ/testUser /add

Active Directory: Differences between Domain Local, Global, Universal Groups

  • Domain Local Groups: can contain users from any domain, but they can only be used to grant access to resources that belong to the same domain where the group is created.
  • Global Groups: can ONLY contain users from a single domain – no external domains. These groups can be used in any trusted external domains from the ingress direction of external (consumer) to internal (source).
  • Universal Groups: may contain accounts and other local, global, or universal groups from the same domain, and they can be used by resources in any domain in the forest with trusts. Unlike Global, Universal groups can be changed frequently without causing global catalog replication.

Fix NetPlWiz and control userpasswords2 Grey-out Options

Problem: netplwiz has a grayed out ‘Users must enter a user name and password to use this computer’ option

Resolution: change DevicePasswordLessBuildVersion version 2 to version 0

Set-ItemProperty -Path "REGISTRY::HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PasswordLess\Device" -Name "DevicePasswordLessBuildVersion" -Value 0

PowerShell: How To Configure Static IP Address

$nicName='NIC1'
$ipaddress='192.168.0.222'
$cidrPrefix=24
$defaultGateway='192.168.0.1'
$dnsServers=@('8.8.8.8','4.4.2.2')
$disableIpv6=$true

function setNic($nicName,$ipAddress,$cidrPrefix,$defaultGateway,$dnsServers,$disableIpv6=$true ){
	$ifIndex=(get-netadapter|?{$_.Name -eq $nicName}).ifIndex
	New-NetIPAddress -InterfaceIndex $ifIndex -IPAddress $ipaddress -PrefixLength $cidrPrefix -DefaultGateway $defaultGateway
	Set-DnsClientServerAddress -InterfaceIndex $ifIndex -ServerAddresses $dnsServers
	if($disableIpv6){Disable-NetAdapterBinding -Name $nicName –ComponentID ms_tcpip6}
}
setNic $nicName $ipAddress $cidrPrefix $defaultGateway $dnsServers $disableIpv6

PowerShell: Enable CredSSP on Windows

PowerShell:

# Enabled WinRM
Enable-PSRemoting -Force

# Enable CredSSP
Enable-WSManCredSSP -Role Server -Force

Legacy Command Line:

# Enable WinRM
winrm quickconfig -quiet
# Entering legacy command line from PowerShell
PS C:\Users\Administrator> cmd
Microsoft Windows [Version 10.0.17763.107]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Users\Administrator>winrm set winrm/config/service/auth @{CredSSP="true"}
Auth
Basic = false
Kerberos = true
Negotiate = true
Certificate = false
CredSSP = true
CbtHardeningLevel = Relaxed

Validation:

# Validate that CredSSP is enabled
PS C:\Users\Administrator> winrm get winrm/config/service/auth | select-string "CredSSP"
CredSSP = true

Windows: How to Create Software Raid 1 Mirror of C:\ Volume or Windows System Drive

Desired Result:

Required Work:

# Check disks
DISKPART> list disk
Disk ### Status Size Free Dyn Gpt
-------- ------------- ------- ------- --- ---
* Disk 0 Online 186 GB 0 B *
Disk 1 Offline 186 GB 186 GB
Disk 2 No Media 0 B 0 B

# Set disks online
DISKPART> select disk 1
DISKPART> online disk
DiskPart successfully onlined the selected disk.

# Clean disk
DISKPART> clean
DiskPart succeeded in cleaning the disk.

# Convert disks to become the same type, either MBR or GPT
DISKPART> convert mbr
DiskPart successfully converted the selected disk to MBR format.

# convert to dynamic as required for software mirroring
DISKPART> select disk 1
Disk 1 is now the selected disk.
DISKPART> convert dynamic
DiskPart successfully converted the selected disk to dynamic format.

# Check for existing volumes
DISKPART> list volume
Volume ### Ltr Label Fs Type Size Status Info
---------- --- ----------- ----- ---------- ------- --------- --------
Volume 0 C NTFS Simple 185 GB Healthy Boot
* Volume 1 System Rese NTFS Simple 549 MB Healthy System
Volume 3 E DVD-ROM 0 B No Media
Volume 4 F SSS_X64FREV UDF CD-ROM 4618 MB Healthy
Volume 5 G Removable 0 B No Media

# Mirror the first stripe of disk 0, which is often 'System Reserved'
DISKPART> select volume 1
Volume 1 is the selected volume.
DISKPART> add disk=1
DiskPart succeeded in adding a mirror to the volume.

# Mirror the first stripe of disk 0, which is often 'Boot'
DISKPART> select volume 0
Volume 0 is the selected volume.
DISKPART> add disk=1
DiskPart succeeded in adding a mirror to the volume.

Extra jibberish:

DISKPART> select disk 1
Disk 1 is now the selected disk.

DISKPART> list part

Partition ### Type Size Offset
------------- ---------------- ------- -------
Partition 3 Dynamic Data 992 KB 31 KB
Partition 1 Dynamic Data 549 MB 1024 KB
Partition 2 Dynamic Data 185 GB 550 MB
Partition 4 Dynamic Data 216 KB 186 GB

DISKPART> Sel part 1
Partition 1 is now the selected partition.

DISKPART> delete partition override
Virtual Disk Service error:
The operation is not supported by the object.
The specified command or parameters are not supported on this system.
DISKPART> convert basic
DiskPart successfully converted the selected disk to basic format.
DISKPART> list part
There are no partitions on this disk to show.
DISKPART> select part 1
The specified partition is not valid.
Please select a valid partition.
There is no partition selected.

# How to break a mirrored volume
diskpart
select volume c
break disk=1
remove disk=1

Windows UAC Error This App has been blocked for your protection mmc.exe taskschd.msc

Error Message:
User Account Control

This App has been blocked for your protection.
A administrator has blocked you from running this app. For more information, contract the administrator.

mmc.exe

Publisher: Unknown
File origin: Hard drive on this computer
"C:\Windows\System32\mmc.exe" "C:\Windows\system32\taskschd.msc" /s
Resolution:

Option 1: Disable UAC

$uacRegKeyHive='REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
$keyName='EnableLUA'
$disable=0

Set-ItemProperty -Path $uacRegKeyHive -Name $keyname -value $disable
Restart-Computer -force

Option 2: Change Cryptographic Service run-as Account

$serviceName='CryptSvc'
$serviceAccount='NT AUTHORITY\NETWORK SERVICE'
$servicePassword=$null
$serviceStart='Automatic'
$service=gwmi win32_service -filter "name='$serviceName'"
$service.change($null,$null,$null,$null,$serviceStart,$null,$serviceAccount,$servicePassword)
sc.exe failure $service.name reset= 0 actions= restart/5000

PowerShell: Add RDS Server Role

Step 0: Searching for RDS Licensing Server

# Pick from 1 of these commands:

Get-ADObject -LDAPFilter "(&(CN=TermServLicensing)(objectClass=serviceConnectionPoint))"

Get-ADObject -Filter {objectClass -eq 'serviceConnectionPoint' -and Name -eq 'TermServLicensing'}

dsquery * -filter "(&(CN=TermServLicensing)(objectClass=serviceConnectionPoint))"

Step 1: Adding RDS Role

# To add Remote Desktop Server Role
# Add-WindowsFeature RDS-RD-Server
Install-WindowsFeature RDS-RD-Server -IncludeManagementTools

# Reboot server for changes to take effect
Restart-Computer -force

Step 2: Config RDS with a License Server

# addRdsServerLicense
# To Configure RDS Role

$mode='PerUser'
$licenseServerName='rdsServer.kimconnect.com'

# Set-RDLicenseConfiguration : The term 'Set-RDLicenseConfiguration' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of
# the name, or if a path was included, verify that the path is correct and try again.
# At line:1 char:1
# + Set-RDLicenseConfiguration -LicenseServer $licenseServerName -Mode $m ...
# + ~~~~~~~~~~~~~~~~~~~~~~~~~~
#     + CategoryInfo          : ObjectNotFound: (Set-RDLicenseConfiguration:String) [], CommandNotFoundException
#     + FullyQualifiedErrorId : CommandNotFoundException
# import-module remoteDesktop
# Set-RDLicenseConfiguration -LicenseServer $licenseServerName -Mode $mode
# restart-computer -Force

# To set licensing mode and/or licensing server
$licensingModes=@{
    'PerDevice'=2
    'PerUser'=4
}
$tsSettings = gwmi -namespace "Root/CIMV2/TerminalServices" Win32_TerminalServiceSetting
$tsSettings.ChangeMode($licensingModes[$mode])
$tsSettings.SetSpecifiedLicenseServerList($licenseServerName)
$tsSettings.GetSpecifiedLicenseServerList()

# Sample output
# PS C:\Windows\system32> $obj.GetSpecifiedLicenseServerList()
# __GENUS          : 2
# __CLASS          : __PARAMETERS
# __SUPERCLASS     :
# __DYNASTY        : __PARAMETERS
# __RELPATH        :
# __PROPERTY_COUNT : 2
# __DERIVATION     : {}
# __SERVER         :
# __NAMESPACE      :
# __PATH           :
# ReturnValue      : 0
# SpecifiedLSList  : {rdsLicense.kimconnect.com}
# PSComputerName   :

# Verify results
lsdiag.msc

Step 3: Optional – Adding RDS Deployments for Advanced Setups

# Error when remote desktop deployment doesn't exist
# Get-RDLicenseConfiguration : A Remote Desktop Services deployment does not exist on rds1.kimconnect.com. This
# operation can be performed after creating a deployment. For information about creating a deployment, run "Get-Help
# New-RDVirtualDesktopDeployment" or "Get-Help New-RDSessionDeployment".
# At line:1 char:1
# + Get-RDLicenseConfiguration
# + ~~~~~~~~~~~~~~~~~~~~~~~~~~
#     + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
#     + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-RDLicenseConfiguration

# Deploy RDS Services
import-module remoteDesktop
$thisServer="$env:computername.$env:USERDNSDOMAIN"
New-SessionDeployment -ConnectionBroker $thisServer -WebAccessServer $thisServer -SessionHost $thisServer

# If additional RD Session Host or RD Licensing
$addServer='rds2.kimconnect.com'
$licenseServerName='rdsLicense.kimconnect.com'
Add-RDServer -Server $addServer -Role RDS-RD-SERVER -ConnectionBroker $thisServer
Set-RDLicenseConfiguration -LicenseServer $licenseServerName -Mode $mode -ConnectionBroker $thisServer

new-rdvirtualdesktopdeployment
new-rdsessioncollection

$connectionBroker=(Get-RDConnectionBrokerHighAvailability -ConnectionBroker).ActiveManagementServer
Get-RDServer -Connectionbroker $connectionBroker

Misc: Other Methods

# Legacy method to configure registry to enable multiple sessions (doesn't work for Windows Server 2016)
$tsRegHive='REGISTRY::HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server'
Set-ItemProperty -Path $tsRegHive -Name fSingleSessionPerUser -value 0
Set-ItemProperty -Path $tsRegHive -Name fdenyTSConnections -value 0
get-itemproperty $tsRegHive

PowerShell: Install Windows Exporter

Installing Prometheus Windows Exporter Client on a List of Windows Servers

# installWindowsExporter.ps1
# version: 0.0.2
# Limitations: this script assumes that the Windows machine has outbound access to github (firewall allowed)
# Current iteration is sequential - next iteration is to run installs in parallel

# User input-variables
$computerNames=@(
    'SERVER001',
    'SERVER002'
)

$parentUrl='https://github.com/prometheus-community/windows_exporter/releases'
$fileExtension='.msi'
$maxDepth=1
$serviceName='windows_exporter'
$resets=30
$restartWaitMs=100000
$maxWaitSeconds=120

function installWindowsExporter{
    param(
        $computernames=$env:computername,
        $parentUrl='https://github.com/prometheus-community/windows_exporter/releases',
        $fileExtension='.msi',
        $maxDepth=1,
        $serviceName='windows_exporter',
        $resets=30,
        $restartWaitMs=100000,
        $searchTimeout=30,
        $maxWaitSeconds=120
    )
    function installWindowsExporterUsingChoco{
        param(
            $parentUrl='https://github.com/prometheus-community/windows_exporter/releases',
            $fileExtension='.msi',
            $maxDepth=1,
            $serviceName='windows_exporter',
            $resets=30,
            $restartWaitMs=100000,
            $searchTimeout=30
        )
    
        # Check whether product is installed before proceeding
        function checkUninstall($serviceName){
            $cpuArchitecture32bitPointerSize=4
            $path=if ([IntPtr]::Size -eq $cpuArchitecture32bitPointerSize) {
                'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
            }else{
                @('HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*',
                  'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*')
            }
            Get-ItemProperty $path |.{process{ if ($_.DisplayName -eq $serviceName -and $_.UninstallString) { $_ } }} |
            Select-Object DisplayName, Publisher, InstallDate, DisplayVersion, UninstallString
        }
        $installed=checkUninstall $serviceName
    
        if(!$installed){
            try{
                # Install Chocolatey
                if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
                    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
                    Set-ExecutionPolicy Bypass -Scope Process -Force;
                    $null=iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))}
                $null=choco install prometheus-windows-exporter.install -y --ignore-checksums
            }catch{
                write-warning $_
                return $false
            }
        }else{
            write-host "$env:computername already has $servicename installed"
        }
        
        try{
            # Set auto start and restart upon failures
            $null=& sc.exe failure $serviceName reset= $resets actions= restart/$restartWaitMs/restart/$restartWaitMs/""/$($restartWaitMs*3)
            Set-Service -Name $serviceName -StartupType 'Automatic'
            write-host "$env:computername now has $servicename set to automatically run and reset at $resets and restart wait-time of $restartWaitMs ms."
            return $(get-service $serviceName).Status -eq 'Running'
        }catch{
            write-warning $_
            return $false
        }
    }

    $results=@()
    $computernames|%{
    $job=invoke-command -computername $_ -scriptblock{
    param($installWindowsExporterUsingChoco,$parentUrl,$fileExtension,$maxDepth,$serviceName,$resets,$restartWaitMs)
    [ScriptBlock]::Create($installWindowsExporterUsingChoco).invoke($parentUrl,$fileExtension,$maxDepth,$serviceName,$resets,$restartWaitMs)
    } -ArgumentList ${function:installWindowsExporterUsingChoco},$parentUrl,$fileExtension,$maxDepth,$serviceName,$resets,$restartWaitMs -AsJob -JobName installPrometheusExporter
    $count=0
    while (($job.State -like "Running") -and ($count -lt $maxWaitSeconds)){
        Start-Sleep -Seconds 1
        $count++
    }
    if ($Job.State -like "Running") { $job | Stop-Job }
    $success=$job | Receive-Job
    $job|Remove-Job
    $result=if($success){
        [pscustomobject]@{$_=$success}
    }else{
        [pscustomobject]@{$_=$false}
    }
    $results+=$result
    }
    return $results
}

$status=installWindowsExporter $computerNames $parentUrl $fileExtension $maxDepth $serviceName $resets $restartWaitMs $maxWaitSeconds
write-host $status

Previous Version: Install Using Github

# installWindowsExporter.ps1
# version: 0.0.1
# Limitations: this script assumes that the Windows machine has outbound access to github (firewall allowed)

# User input-variables
$parentUrl='https://github.com/prometheus-community/windows_exporter/releases'
$fileExtension='.msi'
$maxDepth=1
 
function findDownloadUrl{
    param(
        $startUrl,
        $fileExtension,
        $maxDepth=3
    )
    $simultaneousJobs=8
    $linksChecked=0
    $firstResult=$false
    $timer=[System.Diagnostics.Stopwatch]::StartNew()
    if(!$startUrl){
        write-warning "Cannot start with a blank parent URL"
    }elseif($startUrl -notmatch '/$'){
        $startUrl=$startUrl+'/'
        }
 
    function findFile($parentUrl,$extension){
        $ProgressPreference='SilentlyContinue'
        $ErrorActionPreference='stop'
        if($parentUrl -notmatch '/$'){$parentUrl=$parentUrl+'/'}
        try{
            $page=Invoke-WebRequest $parentUrl -TimeoutSec 10
        }catch{
            return @{'result'=$false;'links'=@()}
            }
        $newLinks=$page.links.href|?{$_ -notlike "*$(Split-Path $parentUrl -parent)"}| `
            sort -Descending|%{$(
                                if($_[0] -eq '/'){
                                    $parentUrl+$_.Substring(1,$_.length-1)
                                }elseif($_ -match '^http'){
                                    $_
                                }else{
                                    $parentUrl+$_
                                }
                            )}|select -Unique
        $matchedExtension=$newLinks|?{$_ -like "*$extension"}|sort -Descending|select -First 1
        if($matchedExtension){
            return @{'result'=$true;'links'=$matchedExtension}
        }elseif($newLinks){
            return @{'result'=$false;'links'=$newLinks}
        }else{
            return @{'result'=$false;'links'=@()}
            } 
    }  
 
    write-host "Scanning $startUrl for file extension $fileExtension"
    $startLinks=.{$result=findFile $startUrl $fileExtension
                    return $result['links']
                    }    
    if($startLinks -eq $null){
        write-warning "There were problems parsing links"
        return $null
    }elseif($startLinks.gettype() -eq [string]){
        return $startLinks
    }
    $knownLinks=$startLinks
 
    foreach ($link in $startLinks){       
        $currentDepth=1
        write-host "Processing link at current depth: $currentDepth"
        $newLinks=@($link) 
        do{ 
            if($i++ -lt $simultaneousJobs -and !(!$newLinks)){
                $thisLink=$newLinks|Select -Unique|select -First 1
                if($newLinks.count -gt 1){
                    $newLinks=$newLinks[1..($newLinks.count-1)]
                }else{
                    $newLinks=@()
                    }
                write-host "Parsing $thisLink"
                $job=start-job -ScriptBlock{
                    param($findFile,$thisLink,$fileExtension)
                    return [ScriptBlock]::Create($findFile).invoke($thisLink,$fileExtension)
                    } -Args ${function:findFile},$thisLink,$fileExtension
                $linksChecked++
            }else{
                do{
                    $results=Get-Job|Receive-Job -wait
                    get-job -State 'Completed'|remove-job                    
                    $results|%{
                        $currentDepth++
                        if($_['result']){
                            write-host "Bingo!" -ForegroundColor Green
                            get-job|remove-job
                            $firstResult=$_['links']
                        }elseif($currentDepth -le $maxDepth){
                            $addLinks=$_['links']|?{$_ -notin $knownLinks}
                            if($addLinks){
                                write-host "Adding new links to depth $currentDepth`:`r`n$(($addLinks|out-string).trim())"
                                $knownLinks+=$addLinks
                                $newLinks=$addLinks+$newLinks
                                }
                            }
                        }
                    $i=(get-job -state 'Running').count
                    }until($i -lt $simultaneousJobs -or $firstResult) 
                }
        }until((!$newLinks -and !$i) -or $firstResult)            
                
        if($firstResult){
            $totalMinutes=[math]::round($timer.Elapsed.TotalMinutes,2)
            write-host "Minutes elapsed: $totalMinutes"
            return $firstResult
            }
    }
 
    $totalMinutes=[math]::round($timer.Elapsed.TotalMinutes,2)
    write-host "$linksChecked links have been checked in $totalMinutes minutes without finding file extension $fileExtension" -ForegroundColor Red
    return $false
}
$windowsExporterUrl=findDownloadUrl $parentUrl $fileExtension $maxDepth
# $fileName=[regex]::match($windowsExporterUrl,'(?:.(?!\/))+$') # this negative lookahead includes the '/' slashes
$fileName=[regex]::match($windowsExporterUrl,'[^/\\&\?]+\.\w{3,4}(?=([\?&].*$|$))')

# $windowsExporterUrl='https://github.com/prometheus-community/windows_exporter/releases/download/v0.18.1/windows_exporter-0.18.1-amd64.msi'
# $fileName='windows_exporter-0.18.1-amd64.msi'
$stageFolder='C:\Temp\'
$msiFile=join-path $stageFolder $fileName

# Faster way of installing if there are no firewall problems
# choco install prometheus-windows-exporter.install

# Download the file
Import-Module BitsTransfer
if(!(test-path $stageFolder)){mkdir $stageFolder}
Start-BitsTransfer -Source $windowsExporterUrl -Destination $msiFile

# Install using MSIEXEC
msiexec /i $msiFile ENABLED_COLLECTORS=os,cpu,cs,logical_disk,net,tcp,hyperv,service,textfile /quiet

# Check whether product is installed
$serviceName='windows_exporter'
function checkUninstall($serviceName){
    $cpuArchitecture32bitPointerSize=4
    $path=if ([IntPtr]::Size -eq $cpuArchitecture32bitPointerSize) {
        'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
    }else{
        @('HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*',
          'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*')
    }
    Get-ItemProperty $path |.{process{ if ($_.DisplayName -eq $serviceName -and $_.UninstallString) { $_ } }} |
    Select-Object DisplayName, Publisher, InstallDate, DisplayVersion, UninstallString
}
checkUninstall $serviceName

# Set auto start and restart upon failures
$serviceName='windows_exporter'
& sc.exe failure $serviceName reset= 30 actions= restart/100000/restart/100000/""/300000
Set-Service -Name $serviceName -StartupType 'Automatic'

PowerShell: Check Whether an Application Is Installed Using Known Service Name

# Check whether product is installed
$serviceName='windows_exporter'
function checkUninstall($serviceName){
    $cpuArchitecture32bitPointerSize=4
    $path=if ([IntPtr]::Size -eq $cpuArchitecture32bitPointerSize) {
        'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
    }else{
        @('HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*',
          'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*')
    }
    Get-ItemProperty $path |.{process{ if ($_.DisplayName -eq $serviceName -and $_.UninstallString) { $_ } }} |
    Select-Object DisplayName, Publisher, InstallDate, DisplayVersion, UninstallString
}
checkUninstall $serviceName