├── Autopilot ├── ConfigMgr Client │ ├── Detect-ConfigMgrClient.ps1 │ └── Install-CCMClient.ps1 └── Get-AutopilotDiagnosticEvents.ps1 ├── CI Examples.ps1 ├── Co-Management Diagram.png ├── ConfigItems ├── No_Interactive_User │ ├── Fix No interactive user login issue.cab │ ├── No_Interactive_User_Discovery.ps1 │ └── No_Interactive_User_Remediation.ps1 ├── SetupConfig_ExtraParameters │ ├── Feature Update - SetupConfig.ini.cab │ ├── SetupConfigCI_Discovery.ps1 │ └── SetupConfigCI_Remediation.ps1 ├── SkypeRemoval │ ├── SkypeDetection_Computer.ps1 │ ├── SkypeDetection_User.ps1 │ ├── SkypeRemediation_Computer.ps1 │ └── SkypeRemediation_User.ps1 ├── SmarterCacheManagement │ ├── CacheConfig_Discovery.ps1 │ └── CacheConfig_Remediation.ps1 ├── Task Sequence Toasts │ ├── README.md │ ├── TaskSequenceToast_Discovery.ps1 │ └── TaskSequenceToast_Remediation.ps1 └── Windows Update Toasts │ ├── README.md │ ├── Toast_Discovery.ps1 │ └── Toast_Remediation.ps1 ├── FrontEnds ├── BIOSServicingGUI │ └── BIOSServicingGUI.ps1 ├── OSDFrontEnd │ ├── FrontEnd.xaml │ ├── Icon.ico │ ├── MainWindow.xaml │ ├── OSDFrontEnd.ps1 │ └── background.png └── Windows Servicing GUI │ ├── BurntToast │ ├── BurntToast.psd1 │ ├── BurntToast.psm1 │ ├── Images │ │ ├── BurntToast-Logo.png │ │ ├── BurntToast.png │ │ └── Thumbs.db │ ├── PSGetModuleInfo.xml │ ├── Private │ │ └── Test-BTAudioPath.ps1 │ ├── Public │ │ ├── New-BTAction.ps1 │ │ ├── New-BTAppId.ps1 │ │ ├── New-BTAudio.ps1 │ │ ├── New-BTBinding.ps1 │ │ ├── New-BTButton.ps1 │ │ ├── New-BTContent.ps1 │ │ ├── New-BTContextMenuItem.ps1 │ │ ├── New-BTHeader.ps1 │ │ ├── New-BTImage.ps1 │ │ ├── New-BTInput.ps1 │ │ ├── New-BTProgressBar.ps1 │ │ ├── New-BTSelectionBoxItem.ps1 │ │ ├── New-BTText.ps1 │ │ ├── New-BTVisual.ps1 │ │ ├── New-BurntToastNotification.ps1 │ │ └── Submit-BTNotification.ps1 │ ├── config.json │ └── lib │ │ └── Microsoft.Toolkit.Uwp.Notifications │ │ ├── Microsoft.Toolkit.Uwp.Notifications.dll │ │ ├── Microsoft.Toolkit.Uwp.Notifications.pdb │ │ └── Microsoft.Toolkit.Uwp.Notifications.xml │ ├── DiskClear.xaml │ ├── GettingReady.xaml │ ├── Icon.ico │ ├── MainWindow.xaml │ ├── Ready.xaml │ ├── Resume Windows 10 Upgrade.url │ ├── RunSilent.exe │ ├── RunSilent.ini │ ├── ServicingGUI.ps1 │ ├── ToastReminder.ps1 │ ├── Welcome.xaml │ └── background.png ├── Get-UpdateErrorCodesFromSQLandCreateDeviceCollections_Sanitized.ps1 ├── Images ├── Branded_Toast_Available_and_Required.png ├── DefaultNotifications.png ├── Toast_No_Logo.png └── Toast_With_Logo.png ├── LICENSE ├── OSD └── unattend.xml ├── README.md ├── Scripts ├── Distribute-UndistributedDeployedApplications.ps1 ├── Generate-HardwareModelCollections.ps1 └── Update-DeploymentTypesForRecentlyCreatedApplications.ps1 ├── WinUpdate Presentation.pptx └── supportcenterinstaller.msi /Autopilot/ConfigMgr Client/Detect-ConfigMgrClient.ps1: -------------------------------------------------------------------------------- 1 | # ConfigMgr Client detection for the Autopilot Scheduled Task installer 2 | 3 | #Set your expected Site Code 4 | $SiteCode = "ABC" 5 | 6 | # Check if client is installed/initialized 7 | $clientVersion = (Get-CimInstance SMS_Client -Namespace root\ccm -ErrorAction SilentlyContinue).ClientVersion 8 | $SMSauthority = (Get-CimInstance SMS_Authority -Namespace root\ccm -ErrorAction SilentlyContinue) 9 | 10 | # Check if scheduled task installer exists (meaning it hasn't executed) 11 | $taskExists = get-scheduledtask -taskname "Configuration Manager Client Retry Task" -ErrorAction SilentlyContinue 12 | 13 | # Check if CCMsetup was downloaded 14 | $ccmsetupdl = Test-Path C:\Windows\Temp\CCMsetup\ccmsetup.exe 15 | 16 | # Check if the ccmsetup service or executable is running 17 | $ccmservice = Get-Service ccmsetup -ErrorAction SilentlyContinue 18 | $ccmsetupexe = Get-Process ccmsetup -ErrorAction SilentlyContinue 19 | 20 | ## This part might be overkill... 21 | # If the client is reporting the installed version + Site code + MP (might be a better way to tell if the client is OK than this, but this was a 'good enough' check) 22 | # OR if the task exists and ccmsetup is downloaded (meaning the task hasn't ran yet) 23 | # OR if the ccmsetup is currently running, mark it as installed. 24 | #If something goes wrong with the client install after the task executes, this shouldn't return anything due to the above checks, so Intune will still see it as applicable and re-execute the installer as a "retry". 25 | IF($clientVersion -and ($SMSauthority.Name -eq "SMS:$SiteCode" -and $SMSauthority.CurrentManagementPoint) -or ($taskExists -and $ccmsetupdl) -or $ccmservice -or $ccmsetupexe){ 26 | Return "Installed" 27 | } 28 | -------------------------------------------------------------------------------- /Autopilot/ConfigMgr Client/Install-CCMClient.ps1: -------------------------------------------------------------------------------- 1 | ## HOW THIS WORKS ## 2 | # Save this script in a folder with an optional "offline" copy of ccmsetup.exe for the client installation. Package it for Intune deployment with the Intune Win32 App Packager 3 | # It's reccomended to deploy this to All Users or a User Group for your Autopilot deployment if you set ESP app requirements, this way it installs as one of the last items 4 | # during ESP. Adjust the Task execution timeout to what works best for your environment. If you use other parameters, feel free to add/modify the task action (https://docs.microsoft.com/en-us/mem/configmgr/core/clients/deploy/about-client-installation-properties) 5 | # NOTE: The task name and location is imporant as this is mimicing a native scheduled task created (and deleted by subsequent executions) by the CCM client install if setup fails. 6 | # this may be deprecated in a later version of MEMCM CB, so use at your own risk. Alternatively, you could probably use this with the detection as a Proactive Remediation, OR, use 7 | # Proactive Remediations to clean up the task installer if you don't feel comfortable mimicking the ConfigMgr Client Rety task does. 8 | 9 | ## Future Improvements to make to this script: 10 | # - Make sure the task doesn't inadvertantly launch during ESP if there's a reboot during any phase after the payload is dropped. 11 | # - check if ESP is still running, if so, postpone task execution until it completes. (check for WWAHost.exe?) 12 | # - If defaultuser0 is still logged in, prevent task execution ^ 13 | # - Add AAD Token Authentication parameters if not using PKI (though I am unsure if this will work to auth against the CMG) 14 | 15 | # Force script to use TLS 1.2 16 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 17 | 18 | # Replace this with your CMG URL from Cloud Management Settings 19 | $CMG_url = "https://yourcmgurl.domain.com/CCM_Proxy_MutualAuth/#########" 20 | 21 | # Replace this with an Internal MP 22 | $MP_url = "https://internalmp.domain.com" 23 | 24 | # Replace this with your PKI CA Issuer (if no PKI, I don't know if you'll be able to do the dynamic ccmsetup download) 25 | $CA_path = "CN=YOUR-CERT-ISSUER-CA, DC=domain, DC=com" 26 | 27 | # Replace this with your Site Code 28 | $SiteCode = "ABC" 29 | 30 | # Minutes to wait for first Scheduled Task Execution. It is reccomended to give it at least 1 minute. 31 | # NOTE: After an Autopilot Deployment, the default Windows Sleep settings on battery is 4 minutes, AC power is 10 minutes. 32 | # This can cause the task execution to be missed if -StartWhenAvailable is not flagged on the task settings. 33 | $Minutes = 1 34 | 35 | ## End of Parameters ## 36 | 37 | # Get Client certificates for CMG authentication - In my environment, I'm using Intune NDES to deploy certificates via SCEP for Client Authentication 38 | $certs = ((Get-ChildItem Cert:\LocalMachine\My) | ? {$_.EnhancedKeyUsageList -like '*Client Authentication*' -and $_.Issuer -eq $CA_path}) 39 | 40 | # Create the temp directory for downloading CCMSetup. This should get cleaned up by Storage Sense or cleanmgr.exe 41 | IF(!(Test-Path C:\Windows\Temp\CCMsetup)){ 42 | New-Item -ItemType Directory -Path C:\Windows\Temp\CCMsetup 43 | } 44 | 45 | IF($certs){ 46 | Foreach($cert in $certs){ 47 | Try{ 48 | $ccmsetup = Invoke-WebRequest -uri "$CMG_url/CCM_Client/ccmsetup.exe" -Certificate $cert -OutFile "C:\Windows\Temp\CCMSetup\ccmsetup.exe" 49 | }Catch{} 50 | IF(Test-Path C:\Windows\Temp\CCMSetup\ccmsetup.exe){ 51 | break 52 | } 53 | break 54 | } 55 | }Else{ 56 | # allows local fallback for ccmsetup.exe if you aren't using internal PKI 57 | IF(Test-Path .\ccmsetup.exe){ 58 | Copy-Item .\ccmsetup.exe C:\Windows\Temp\CCMsetup\ccmsetup.exe -Force 59 | } 60 | } 61 | 62 | # Create the Scheduled Task installer 63 | $A = New-ScheduledTaskAction -Execute C:\Windows\Temp\CCMSetup\ccmsetup.exe -Argument "CCMHOSTNAME=$CMG_url SMSSiteCode=$SiteCode SMSMP=$MP_url /NOCRLCHECK /USEPKICERT" 64 | $T = New-ScheduledTaskTrigger -Daily -At ([System.DateTime]::Now).AddMinutes($Minutes) 65 | [Array]$T += New-ScheduledTaskTrigger -AtLogOn 66 | $P = New-ScheduledTaskPrincipal "NT Authority\System" 67 | $S = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable 68 | $task = New-ScheduledTask -Action $A -Trigger $T -Principal $P -Settings $S 69 | Register-ScheduledTask -TaskName "Configuration Manager Client Retry Task" -InputObject $Task -TaskPath 'Microsoft\Microsoft\Configuration Manager' 70 | -------------------------------------------------------------------------------- /Autopilot/Get-AutopilotDiagnosticEvents.ps1: -------------------------------------------------------------------------------- 1 | ############################ Skip to line 112 for the meat and potatoes ############################ 2 | ### This is pieced together from Cody Mathis' PSCCMClient module: https://github.com/CodyMathis123/PSCCMClient 3 | 4 | enum Severity { 5 | None 6 | Informational 7 | Warning 8 | Error 9 | } 10 | 11 | enum CMLogType { 12 | FullCMTrace 13 | SimpleCMTrace 14 | } 15 | 16 | class CMLogEntry { 17 | [string]$Message 18 | [Severity]$Type 19 | [string]$Component 20 | [int]$Thread 21 | [datetime]$Timestamp 22 | hidden [string]$Offset 23 | 24 | CMLogEntry() { 25 | } 26 | 27 | CMLogEntry([string]$Message, [Severity]$Type, [string]$Component, [int]$Thread) { 28 | $this.Message = $Message 29 | $this.Type = $Type 30 | $this.Component = $Component 31 | $this.Thread = $Thread 32 | } 33 | 34 | [void]ResolveTimestamp([array]$LogLineSubArray, [CMLogType]$Type) { 35 | [string]$DateString = [string]::Empty 36 | [string]$TimeString = [string]::Empty 37 | [string]$TimeStringRaw = [string]::Empty 38 | 39 | try { 40 | switch ($Type) { 41 | FullCMTrace { 42 | $DateString = $LogLineSubArray[3] 43 | $TimeStringRaw = $LogLineSubArray[1] 44 | $TimeString = $TimeStringRaw.Substring(0, 12) 45 | } 46 | SimpleCMTrace { 47 | $DateTimeString = $LogLineSubArray[1] 48 | $DateTimeStringArray = $DateTimeString.Split([char]32, [System.StringSplitOptions]::RemoveEmptyEntries) 49 | $DateString = $DateTimeStringArray[0] 50 | $TimeStringRaw = $DateTimeStringArray[1] 51 | $TimeString = $TimeStringRaw.Substring(0, 12) 52 | } 53 | } 54 | } 55 | catch { 56 | if ($null -eq $DateString) { 57 | Write-Warning "Failed to split DateString [LogLineSubArray: $LogLineSubArray] [Error: $($_.Exception.Message)]" 58 | } 59 | elseif ($null -eq $TimeString) { 60 | Write-Warning "Failed to split TimeString [LogLineSubArray: $LogLineSubArray] [Error: $($_.Exception.Message)]" 61 | } 62 | } 63 | $DateStringArray = $DateString.Split([char]45) 64 | 65 | $MonthParser = $DateStringArray[0] -replace '\d', 'M' 66 | $DayParser = $DateStringArray[1] -replace '\d', 'd' 67 | 68 | $DateTimeFormat = [string]::Format('{0}-{1}-yyyyHH:mm:ss.fff', $MonthParser, $DayParser) 69 | $DateTimeString = [string]::Format('{0}{1}', $DateString, $TimeString) 70 | try { 71 | $This.Timestamp = [datetime]::ParseExact($DateTimeString, $DateTimeFormat, $null) 72 | # try{ 73 | $this.Offset = $TimeStringRaw.Substring(12, 4) 74 | # } 75 | # catch { 76 | # $this.Offset = "+000" 77 | # } 78 | } 79 | catch { 80 | Write-Warning "Failed to parse [DateString: $DateString] [TimeString: $TimeString] with [Parser: $DateTimeFormat] [Error: $($_.Exception.Message)]" 81 | } 82 | } 83 | 84 | [bool]TestTimestampFilter([datetime]$TimestampGreaterThan, [datetime]$TimestampLessThan) { 85 | return $this.Timestamp -ge $TimestampGreaterThan -and $this.Timestamp -le $TimestampLessThan 86 | } 87 | 88 | [string]ConvertToCMLogLine() { 89 | return [string]::Format('' 90 | , $this.Message 91 | , $this.Timestamp.ToString('HH:mm:ss.fffzz') 92 | , $this.Offset 93 | , $this.Timestamp.ToString('MM-dd-yyyy') 94 | , $this.Component 95 | , [int]$this.Type 96 | , $this.Thread) 97 | } 98 | } 99 | 100 | function ConvertTo-CCMLogFile { 101 | param( 102 | [CMLogEntry[]]$CMLogEntries, 103 | [string]$LogPath 104 | ) 105 | $LogContent = foreach ($Entry in ($CMLogEntries | Sort-Object -Property Timestamp)) { 106 | $Entry.ConvertToCMLogLine() 107 | } 108 | 109 | Set-Content -Path $LogPath -Value $LogContent -Force 110 | } 111 | ######################################################################################### 112 | 113 | # Create our Diagnostic Collection Folders 114 | IF(!(Test-Path C:\Windows\CCM\Logs\AutopilotDiagnostics)){ 115 | New-Item -ItemType Directory -Path C:\Windows\CCM\Logs\AutopilotDiagnostics 116 | } 117 | IF(!(Test-Path C:\Windows\CCM\Logs\AutopilotDiagnostics\IntuneManagementExtension)){ 118 | New-Item -ItemType Directory -Path C:\Windows\CCM\Logs\AutopilotDiagnostics\IntuneManagementExtension 119 | } 120 | 121 | # Define registry paths 122 | $provisioningPath = "registry::HKEY_LOCAL_MACHINE\software\microsoft\provisioning\*" 123 | $autopilotDiagPath1 = "registry::HKEY_LOCAL_MACHINE\software\microsoft\provisioning\Diagnostics\Autopilot" 124 | $autopilotDiagPath2 = "registry::HKEY_LOCAL_MACHINE\software\microsoft\provisioning\Diagnostics\Autopilot\*" 125 | $omadmPath = "registry::HKEY_LOCAL_MACHINE\software\microsoft\provisioning\OMADM\*" 126 | $esppath = "registry::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\Autopilot\EnrollmentStatusTracking\ESPTrackingInfo\Diagnostics" 127 | $msiPath = "registry::HKEY_LOCAL_MACHINE\Software\Microsoft\EnterpriseDesktopAppManagement" 128 | $officePath = "registry::HKEY_LOCAL_MACHINE\Software\Microsoft\OfficeCSP\*" 129 | $sidecarPath = "registry::HKEY_LOCAL_MACHINE\Software\Microsoft\IntuneManagementExtension\Win32Apps\*" 130 | $enrollmentsPath = "registry::HKEY_LOCAL_MACHINE\Software\Microsoft\enrollments\*" 131 | 132 | # Get registry info 133 | $ProvisioningValues = Get-ItemProperty $provisioningPath 134 | $APDiagnosticvalues1 = Get-ItemProperty $autopilotDiagPath1 135 | $APDiagnosticvalues2 = Get-ItemProperty $autopilotDiagPath2 136 | $OMADMValues = Get-ItemProperty $omadmPath 137 | $ESPValues = Get-ItemProperty $esppath -ErrorAction SilentlyContinue 138 | $msiValues = Get-ItemProperty $msiPath -ErrorAction SilentlyContinue 139 | $OfficeValues = Get-ItemProperty $officePath 140 | $SidecarValues = Get-ItemProperty $sidecarPath 141 | $EnrollmentsValues = Get-ItemProperty $enrollmentsPath 142 | 143 | $RegistryInfo = @($ProvisioningValues, $APDiagnosticvalues1, $APDiagnosticvalues2, $OMADMValues, $ESPValues, $msiValues, $OfficeValues, $SidecarValues, $EnrollmentsValues) 144 | 145 | $PropsToExclude = @('PSChildName', 'PSParentPath', 'PSPath', 'PSProvider') 146 | 147 | # Convert reg key entries to CCMLog entries 148 | $ConvertedLog = Foreach ( $RegKey in ($RegistryInfo)) { 149 | foreach($RegValue in $RegKey){ 150 | $ValueProps = ($RegValue | Get-Member -MemberType NoteProperty).Name 151 | 152 | $LogContent = foreach ($property in $ValueProps) { 153 | if ($property -notin $PropsToExclude) { 154 | [string]::Concat($property, " : ", $($RegValue.$property), [System.Environment]::NewLine) 155 | } 156 | } 157 | [CMLogEntry]::new($logcontent, [severity]::Informational, $RegValue.PSPath.Replace('Microsoft.PowerShell.Core\Registry::', ''), 0) 158 | } 159 | } 160 | ConvertTo-CCMLogFile -CMLogEntries $ConvertedLog -LogPath "C:\Windows\CCM\Logs\AutopilotDiagnostics\AutopilotRegistryKeys.log" 161 | 162 | # Collect Intune Management Extension Logs 163 | Copy-Item C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\* C:\Windows\CCM\Logs\AutopilotDiagnostics\IntuneManagementExtension -ErrorAction SilentlyContinue 164 | 165 | $EventLogs = @( 166 | 'microsoft-windows-moderndeployment-diagnostics-provider' 167 | 'microsoft-windows-devicemanagement-enterprise-diagnostics-provider' 168 | 'microsoft-windows-aad' 169 | 'microsoft-windows-shell-core' 170 | 'microsoft-windows-user device registration' 171 | ) 172 | 173 | Foreach($EventLog in $EventLogs){ 174 | 175 | $TestPass = Try{ Get-WinEvent -ProviderName "$EventLog" -MaxEvents 1 -ErrorAction Stop } Catch { $null} 176 | 177 | IF($TestPass){ 178 | $ConvertedLog = (Get-WinEvent -ProviderName "$EventLog") | select TimeCreated,ID,OpcodeDisplayName,Message | foreach { $E = [cmlogentry]::new($_.Message, [severity]::Informational, $_.OpcodeDisplayName, 1);$E.Timestamp = Get-Date $_.timecreated;Write-Output $e} 179 | ConvertTo-CCMLogFile -CMLogEntries $ConvertedLog -LogPath "C:\Windows\ccm\logs\AutopilotDiagnostics\$EventLog.log" 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /CI Examples.ps1: -------------------------------------------------------------------------------- 1 | # ClientSide Error Code 2 | $FailedUpdates = Get-WmiObject “CCM_SoftwareBase" -Namespace "ROOT\ccm\ClientSDK" | Where-Object {$_.UpdateID -and $_.ErrorCode} 3 | 4 | IF($FailedUpdates){ 5 | Foreach($Update in $FailedUpdates){ 6 | $Update.ErrorCode 7 | } 8 | } 9 | 10 | 11 | #Discovery 12 | $FailedUpdates = Get-WmiObject “CCM_SoftwareBase" -Namespace "ROOT\ccm\ClientSDK" | Where-Object {$_.UpdateID -and $_.ErrorCode} 13 | 14 | IF($FailedUpdates){ 15 | Foreach($Update in $FailedUpdates){ 16 | Switch($Update.ErrorCode){ 17 | 2149842976 {Return "Non-Compliant"} 18 | 2147944003 {Return "Non-Compliant"} 19 | default {Return "Compliant"} 20 | } 21 | } 22 | }ELSE{ 23 | Return "Compliant" 24 | } 25 | 26 | 27 | 28 | #Remediation 29 | $FailedUpdates = Get-WmiObject “CCM_SoftwareBase" -Namespace "ROOT\ccm\ClientSDK" | Where-Object {$_.UpdateID -and $_.ErrorCode} 30 | 31 | IF($FailedUpdates){ 32 | Foreach($Update in $FailedUpdates){ 33 | ## Operation did not complete because there is no logged-on interactive user. 34 | IF($Update.ErrorCode -eq 2149842976){ 35 | 36 | # Stop Windows Update Services 37 | Stop-Service wuauserv, bits, appidsvc 38 | Start-Sleep -Seconds 5 39 | Stop-Service cryptsvc -ErrorAction SilentlyContinue 40 | 41 | # Check for/remove previous run 42 | $softDistbackup = "C:\Windows\SoftwareDistribution.bak" 43 | IF(Test-Path $softDistbackup){ 44 | Remove-Item $softDistbackup -Recurse -Force 45 | } 46 | 47 | # Rename SoftwareDistribution to $_.bak 48 | Rename-Item C:\Windows\SoftwareDistribution -NewName SoftwareDistribution.bak 49 | 50 | # Restart Windows Update Services 51 | Start-Service wuauserv, bits, appidsvc, cryptsvc 52 | } 53 | ## There is not enough space on the disk. 54 | IF($Update.ErrorCode -eq 2147942512){ 55 | 56 | $UIResourceMgr = New-Object -ComObject UIResource.UIResourceMgr 57 | 58 | $Cache = $UIResourceMgr.GetCacheInfo() 59 | 60 | $Cache.GetCacheElements() | 61 | foreach { 62 | $Cache.DeleteCacheElement($_.CacheElementID) 63 | } 64 | } 65 | ## Fatal Eror during Installation - Rescan WSUS 66 | IF($Update.ErrorCode -eq 2147944003){ 67 | # Reset Authorization with WSUS 68 | & C:\Windows\System32\wuauclt.exe /ResetAuthorization 69 | Start-Sleep -Seconds 10 70 | # Software Update Scan 71 | Invoke-WmiMethod -Namespace root\CCM -Class SMS_Client -Name TriggerSchedule -ArgumentList "{00000000-0000-0000-0000-000000000113}" | Out-Null 72 | Start-Sleep -Seconds 60 73 | # Update Deployment Evaluation 74 | Invoke-WmiMethod -Namespace root\CCM -Class SMS_Client -Name TriggerSchedule -ArgumentList "{00000000-0000-0000-0000-0000000000108}" | Out-Null 75 | } 76 | } 77 | } 78 | 79 | 80 | -------------------------------------------------------------------------------- /Co-Management Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SysBehr/BehrNecessities/49413c3c41c5b5cb966ff6bca236b27d6ec656d0/Co-Management Diagram.png -------------------------------------------------------------------------------- /ConfigItems/No_Interactive_User/Fix No interactive user login issue.cab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SysBehr/BehrNecessities/49413c3c41c5b5cb966ff6bca236b27d6ec656d0/ConfigItems/No_Interactive_User/Fix No interactive user login issue.cab -------------------------------------------------------------------------------- /ConfigItems/No_Interactive_User/No_Interactive_User_Discovery.ps1: -------------------------------------------------------------------------------- 1 | $SoftwareBase = Get-WmiObject -Query "SELECT * FROM CCM_SoftwareBase" -Namespace "ROOT\ccm\ClientSDK" 2 | $Updates = $SoftwareBase | Where-Object {$_.UpdateID} 3 | $ErrorCodes = $Updates | % {$_.ErrorCode} 4 | 5 | IF($ErrorCodes){ 6 | Foreach($Update in $Updates){ 7 | Switch($Update.ErrorCode){ 8 | 2149842976 {Return "Non-Compliant"} 9 | default {Return "Compliant"} 10 | } 11 | } 12 | }Else{ 13 | Return "Compliant" 14 | } -------------------------------------------------------------------------------- /ConfigItems/No_Interactive_User/No_Interactive_User_Remediation.ps1: -------------------------------------------------------------------------------- 1 | $badupdate = Get-ChildItem -Path C:\Windows\SoftwareDistribution\Download\ -Recurse -filter WindowsUpdateBox.exe 2 | If($badupdate){ 3 | Remove-Item $badupdate.Directory -Recurse -Force 4 | } 5 | -------------------------------------------------------------------------------- /ConfigItems/SetupConfig_ExtraParameters/Feature Update - SetupConfig.ini.cab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SysBehr/BehrNecessities/49413c3c41c5b5cb966ff6bca236b27d6ec656d0/ConfigItems/SetupConfig_ExtraParameters/Feature Update - SetupConfig.ini.cab -------------------------------------------------------------------------------- /ConfigItems/SetupConfig_ExtraParameters/SetupConfigCI_Discovery.ps1: -------------------------------------------------------------------------------- 1 | # Note: These should be created as separate settings for the CI - modify the remdiation script to replace as many parameters as you need to set/enforce. 2 | 3 | Param( 4 | [string] 5 | $SettingName="BitLocker", #Change this for each line item you want to check for. BitLocker, Priority, Compat 6 | 7 | <# 8 | [string] 9 | $SettingName="Compat", 10 | 11 | [string] 12 | $SettingName="Priority", 13 | #> 14 | 15 | [string] 16 | $iniFile = "$($env:SystemDrive)\Users\Default\AppData\Local\Microsoft\Windows\WSUS\SetupConfig.ini" 17 | ) 18 | 19 | Try { 20 | If (Test-Path -Path $iniFile -ErrorAction Stop) { 21 | $Setupfile = (Get-Content $iniFile) 22 | [bool]$FoundMatch = $false 23 | ForEach ($line in $setupfile) { 24 | If ($line -like "$($SettingName)*") { 25 | [bool]$FoundMatch = $true 26 | Return $line.replace("$($SettingName)=", "") 27 | } 28 | } 29 | If (!($FoundMatch)) { 30 | Return "NonCompliant" 31 | } 32 | } 33 | Else { 34 | Return "NonCompliant" 35 | } 36 | } 37 | Catch { 38 | Return "NonCompliant" 39 | } 40 | -------------------------------------------------------------------------------- /ConfigItems/SetupConfig_ExtraParameters/SetupConfigCI_Remediation.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | [SetupConfig] 3 | Priority=High 4 | BitLocker=AlwaysSuspend 5 | Compat=IgnoreWarning 6 | #> 7 | 8 | Param( 9 | 10 | [string] 11 | $ActualValue, #SCCM passes in expected value as a parameter to this remediation script. This will catch it. 12 | 13 | [string] 14 | $iniFile = "$($env:SystemDrive)\Users\Default\AppData\Local\Microsoft\Windows\WSUS\SetupConfig.ini", 15 | 16 | [string] 17 | $iniHeader = "[SetupConfig]", 18 | 19 | [HashTable] #All items in the this table will be added to the INI. 20 | $Settings = @{ 21 | "BitLocker"="AlwaysSuspend"; 22 | "Compat"="IgnoreWarning"; 23 | "Priority"="High" 24 | }, 25 | 26 | <# 27 | #In your CI, you would use one of these per item being checked. 28 | #So for your BitLocker check, just use the Bitlocker item below and comment out the above full table above. 29 | [HashTable] 30 | $Settings = @{ 31 | "BitLocker"="AlwaysSuspend" 32 | }, 33 | 34 | [HashTable] 35 | $Settings = @{ 36 | "Compat"="IgnoreWarning" 37 | }, 38 | 39 | [HashTable] 40 | $Settings = @{ 41 | "Priority"="High" 42 | }, 43 | #> 44 | 45 | [Switch] 46 | $Remove = $False 47 | ) 48 | 49 | $iniSetupConfigContent = @() 50 | $MatchingLines = @() 51 | $OutPutArray = @() 52 | 53 | Try { 54 | 55 | If(!(Test-Path -Path $iniFile -ErrorAction SilentlyContinue)) { 56 | $iniSetupConfigContent += $iniHeader 57 | } 58 | Else { 59 | $iniSetupConfigContent += (Get-Content $iniFile) 60 | } 61 | 62 | If($Settings) { 63 | ForEach($SettingName in $Settings.Keys) 64 | { 65 | $MatchingLines += $iniSetupConfigContent -like ("*$($SettingName)*") 66 | } 67 | 68 | ForEach ($Line in $iniSetupConfigContent) 69 | { 70 | If($Line -NotIn $MatchingLines -and $Line -ne "") 71 | { 72 | $OutPutArray += $Line 73 | } 74 | } 75 | 76 | If(!($Remove.IsPresent)) { 77 | ForEach($SettingName in $Settings.Keys) 78 | { 79 | $NewValue = "{0}={1}" -f $SettingName, $Settings[$SettingName] 80 | $OutPutArray += $NewValue 81 | } 82 | } 83 | } 84 | 85 | New-Item -Path $iniFile -ItemType File -Force | Out-Null 86 | $OutPutArray | Out-File -FilePath $iniFile -Force | Out-Null 87 | $iniSetupConfigContent = $null 88 | $OutPutArray = $null 89 | } 90 | Catch { 91 | Write-Output $Error[0] 92 | } 93 | -------------------------------------------------------------------------------- /ConfigItems/SkypeRemoval/SkypeDetection_Computer.ps1: -------------------------------------------------------------------------------- 1 | $skypeShortcut = Get-Item "$env:ProgramData\Microsoft\Windows\Start Menu\Programs\Skype for Business.lnk" -ErrorAction SilentlyContinue 2 | $skypeExtra = Get-Item "$env:ProgramData\Microsoft\Windows\Start Menu\Programs\Microsoft Office Tools\Skype for Business Recording Manager.lnk" -ErrorAction SilentlyContinue 3 | 4 | $skypeIE64 = Get-ChildItem "HKLM:\Software\Microsoft\Internet Explorer\Extensions\*" | % { Get-ItemProperty $_.Name.Replace("HKEY_LOCAL_MACHINE","HKLM:") | Where-Object {$_.'(default)' -eq "Lync Click to Call"} } 5 | $skypeIE86 = Get-ChildItem "HKLM:\Software\WOW6432Node\Microsoft\Internet Explorer\Extensions\*" | % { Get-ItemProperty $_.Name.Replace("HKEY_LOCAL_MACHINE","HKLM:") | Where-Object {$_.'(default)' -eq "Lync Click to Call"} } 6 | 7 | $skypeExtm = Get-Item "HKLM:\SOFTWARE\Microsoft\Office\Outlook\Addins\UCAddin.LyncAddin.1" -ErrorAction SilentlyContinue 8 | 9 | IF($skypeShortcut -or $skypeExtra -or $skypeIE64 -or $skypeIE86 -or $skypeExtm){ 10 | Return "Non-Compliant" 11 | }Else{ 12 | Return "Compliant" 13 | } 14 | -------------------------------------------------------------------------------- /ConfigItems/SkypeRemoval/SkypeDetection_User.ps1: -------------------------------------------------------------------------------- 1 | # User 2 | $skypeExtu = Get-Item "HKCU:\Software\Microsoft\Office\Outlook\Addins\UCAddin.LyncAddin.1" -ErrorAction SilentlyContinue 3 | $skypeAutorun = Get-ItemProperty HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run -Name Lync -ErrorAction SilentlyContinue 4 | 5 | IF($skypeExtu -or $skypeAutorun){ 6 | Return "Non-Compliant" 7 | }Else{ 8 | Return "Compliant" 9 | } 10 | -------------------------------------------------------------------------------- /ConfigItems/SkypeRemoval/SkypeRemediation_Computer.ps1: -------------------------------------------------------------------------------- 1 | $skypeShortcut = Get-Item "$env:ProgramData\Microsoft\Windows\Start Menu\Programs\Skype for Business.lnk" -ErrorAction SilentlyContinue 2 | $skypeExtra = Get-Item "$env:ProgramData\Microsoft\Windows\Start Menu\Programs\Microsoft Office Tools\Skype for Business Recording Manager.lnk" -ErrorAction SilentlyContinue 3 | 4 | $skypeIE64 = Get-ChildItem "HKLM:\Software\Microsoft\Internet Explorer\Extensions\*" | % { Get-ItemProperty $_.Name.Replace("HKEY_LOCAL_MACHINE","HKLM:") | Where-Object {$_.'(default)' -eq "Lync Click to Call"} } 5 | $skypeIE86 = Get-ChildItem "HKLM:\Software\WOW6432Node\Microsoft\Internet Explorer\Extensions\*" | % { Get-ItemProperty $_.Name.Replace("HKEY_LOCAL_MACHINE","HKLM:") | Where-Object {$_.'(default)' -eq "Lync Click to Call"} } 6 | 7 | $skypeExtm = Get-Item "HKLM:\SOFTWARE\Microsoft\Office\Outlook\Addins\UCAddin.LyncAddin.1" -ErrorAction SilentlyContinue 8 | 9 | IF($skypeShortcut){ Remove-Item $skypeShortcut } 10 | 11 | IF($skypeExtra){ Remove-Item $skypeExtra } 12 | 13 | IF($skypeIE64){ Remove-Item $skypeIE64.PSPath } 14 | 15 | IF($skypeExtm) { Remove-ITem $skypeEXtm.PSPath } 16 | 17 | IF($skypeIE86) { Remove-ITem $skypeIE86.PSPath } 18 | -------------------------------------------------------------------------------- /ConfigItems/SkypeRemoval/SkypeRemediation_User.ps1: -------------------------------------------------------------------------------- 1 | # User 2 | $skypeExtu = Get-Item "HKCU:\Software\Microsoft\Office\Outlook\Addins\UCAddin.LyncAddin.1" -ErrorAction SilentlyContinue 3 | $skypeAutorun = Get-ItemProperty HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run -Name Lync -ErrorAction SilentlyContinue 4 | 5 | IF($skypeExtu) { Remove-ITem $skypeExtu.PSPath } 6 | 7 | IF($skypeAutorun){ Remove-ItemProperty $skypeAutorun.PSPath -Name "Lync" } 8 | -------------------------------------------------------------------------------- /ConfigItems/SmarterCacheManagement/CacheConfig_Discovery.ps1: -------------------------------------------------------------------------------- 1 | $CCMCache = Get-WmiObject -Namespace "ROOT\CCM\SoftMgmtAgent" -Class CacheConfig 2 | $c = Gwmi Win32_Volume | Where {$_.Name -eq 'C:\'} | Select Capacity,FreeSpace 3 | 4 | $TotalDiskSpace = [Math]::Round($c.Capacity/1GB,2) 5 | $FreeDiskSpace = [Math]::Round($c.FreeSpace/1GB,2) 6 | 7 | $TotalCacheSize = [Math]::Round($CCMCache.size/1000,2) 8 | $UsedCacheSize = [Math]::Round(((Get-ChildItem $CCMCache.Location -Recurse -Force | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum / 1GB),2) 9 | 10 | $DiskFreePercent = [Math]::Round(($FreeDiskSpace/$TotalDiskSpace),2)*100 11 | $CacheUsedPercent = [Math]::Round($UsedCacheSize/$TotalCacheSize,2)*100 12 | 13 | $DesiredCacheSize = <# 10% of Disk #> ($TotalDiskSpace * .10) * 1000 14 | $MinimumCacheSize = 51240 15 | 16 | #If Desired Cache is greater than MB free on disk, set it to 50% of available FreeSPace 17 | If($DesiredCacheSize -gt ($FreeDiskSpace * 1000)){ 18 | $DesiredCacheSize = [Math]::Round(((($DiskFreePercent/2) * .01) * $TotalDiskSpace) * 1000) 19 | } 20 | 21 | IF($DesiredCacheSize -lt $MinimumCacheSize){ 22 | $DesiredCacheSize = $MinimumCacheSize 23 | } 24 | 25 | IF($CCMCache.Size -lt $DesiredCacheSize){ 26 | Return $True 27 | } 28 | 29 | IF(($CacheUsedPercent -ge 75) -or ($DiskFreePercent -le 5) -or ($ChangeCacheFlag)){ 30 | Return $True 31 | } 32 | 33 | Return $False 34 | -------------------------------------------------------------------------------- /ConfigItems/SmarterCacheManagement/CacheConfig_Remediation.ps1: -------------------------------------------------------------------------------- 1 | $CCMCache = Get-WmiObject -Namespace "ROOT\CCM\SoftMgmtAgent" -Class CacheConfig 2 | $c = Gwmi Win32_Volume | Where {$_.Name -eq 'C:\'} | Select Capacity,FreeSpace 3 | 4 | $TotalDiskSpace = [Math]::Round($c.Capacity/1GB,2) 5 | $FreeDiskSpace = [Math]::Round($c.FreeSpace/1GB,2) 6 | 7 | $TotalCacheSize = [Math]::Round($CCMCache.size/1000,2) 8 | $UsedCacheSize = [Math]::Round(((Get-ChildItem $CCMCache.Location -Recurse -Force | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum / 1GB),2) 9 | 10 | $DiskFreePercent = [Math]::Round(($FreeDiskSpace/$TotalDiskSpace),2)*100 11 | $CacheUsedPercent = [Math]::Round($UsedCacheSize/$TotalCacheSize,2)*100 12 | 13 | $DesiredCacheSize = <# 10% of Disk #> ($TotalDiskSpace * .10) * 1000 14 | $MinimumCacheSize = 5120 15 | 16 | #If Desired Cache is greater than MB free on disk, set it to 50% of available FreeSPace 17 | If($DesiredCacheSize -gt ($FreeDiskSpace * 1000)){ 18 | $DesiredCacheSize = [Math]::Round(((($DiskFreePercent/2) * .01) * $TotalDiskSpace) * 1000) 19 | } 20 | 21 | IF($DesiredCacheSize -lt $MinimumCacheSize){ 22 | $DesiredCacheSize = $MinimumCacheSize 23 | } 24 | 25 | IF($CCMCache.Size -lt $DesiredCacheSize){ 26 | $ChangeCacheFlag = $True 27 | }else{ 28 | $ChangeCacheFlag = $False 29 | } 30 | 31 | #"$($DiskFreePercent)% of disk free" 32 | #"$($CacheUsedPercent)% of cache used" 33 | 34 | IF(($CacheUsedPercent -ge 75) -or ($DiskFreePercent -le 5) -or ($ChangeCacheFlag)){ 35 | $ClearCacheFlag = $True 36 | } 37 | Else{ 38 | $ClearCacheFlag = $False 39 | } 40 | 41 | IF($ClearCacheFlag){ 42 | ## Get Cache Elements 43 | $CMObject = New-Object -ComObject "UIResource.UIResourceMGR" 44 | $CMCacheObject = $CMObject.GetCacheInfo() 45 | $CMCacheElementObject = $CMCacheObject.GetCacheElements() 46 | 47 | ## Loop the cache, deleting any object that isn't running in the current cache directory 48 | Foreach ($i in $CMCacheElementObject){ 49 | IF(($i.LastReferenceTime.AddHours(-4) -lt (Get-Date).AddDays(-30)) -or ((Get-Item $i.Location).LastWriteTime -lt (Get-Date).AddDays(-30))){ 50 | $CMCacheObject.DeleteCacheElement($i.CacheElementID) 51 | } 52 | } 53 | 54 | } 55 | 56 | IF($ChangeCacheFlag){ 57 | # Change CacheSize 58 | IF($CCMCache.Location -ne "C:\Windows\ccmcache"){ 59 | $CCMCache.Location = "C:\Windows\ccmcache" 60 | } 61 | $CCMCache.Size = $DesiredCacheSize 62 | $CCMCache.Put() 63 | 64 | # restart SMS Agent Host 65 | Restart-Service ccmexec -Force 66 | Start-Sleep -Seconds 5 67 | # Request Machine Policy 68 | Invoke-WmiMethod -Namespace root\ccm -Class SMS_Client -Name RequestMachinePolicy -ArgumentList 1 | Out-Null 69 | Start-Sleep -Seconds 5 70 | Invoke-WmiMethod -Namespace root\CCM -Class SMS_Client -Name TriggerSchedule '{00000000-0000-0000-0000-000000000021}' | Out-Null 71 | Start-Sleep -Seconds 5 72 | Invoke-WmiMethod -Namespace root\CCM -Class SMS_Client -Name TriggerSchedule '{00000000-0000-0000-0000-000000000121}' | Out-Null 73 | } 74 | -------------------------------------------------------------------------------- /ConfigItems/Task Sequence Toasts/README.md: -------------------------------------------------------------------------------- 1 | Instructions: 2 | - Test the scripts & modify with your branding. 3 | - Create a CI with the discovery and remediation scripts. Ensure it runs in the user context. 4 | - Compliance is an integer value and it should return 0 to be compliant since it's looking for deployed task sequences. -------------------------------------------------------------------------------- /ConfigItems/Task Sequence Toasts/TaskSequenceToast_Discovery.ps1: -------------------------------------------------------------------------------- 1 | ## Displays a Windows 10 Toast Notification for Task Sequences 2 | ## SCCM Configuration Item Discovery Script 3 | ## Set the Compliance setting as an Integer equaling Zero. There is handling for Required vs Available count in the Remediation script. 4 | # Author: Colin Wilkins @sysBehr 5 | # Modified: 08/06/2019 6 | # Version: 1.0 7 | 8 | $softwareCenter = New-Object -ComObject "UIResource.UIResourceMgr" 9 | 10 | $AvailableTSList = $Softwarecenter.GetAvailableApplications() | Where-Object {$_.IsAssigned -ne 1} 11 | $RequiredTSList = $Softwarecenter.GetAvailableApplications() | Where-Object {$_.IsAssigned -eq 1} 12 | 13 | Return ($AvailableTSList.Name.Count + $RequiredTSList.Name.Count) 14 | -------------------------------------------------------------------------------- /ConfigItems/Task Sequence Toasts/TaskSequenceToast_Remediation.ps1: -------------------------------------------------------------------------------- 1 | ## Displays a Windows 10 Toast Notification for Task Sequences 2 | ## SCCM Configuration Item Remediation Script 3 | 4 | ## References 5 | # Source Script/Idea: https://smsagent.wordpress.com/2018/06/15/using-windows-10-toast-notifications-with-configmgr-application-deployments/ 6 | # Options for audio: https://docs.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-audio#attributes-and-elements 7 | # Toast content schema: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/toast-schema 8 | # Author: Colin Wilkins @sysBehr 9 | # Modified: 08/06/2019 10 | # Version: 1.2 11 | 12 | # Required parameters 13 | # |-- Customizable --| 14 | $Title = "Windows 10 Feature Update" 15 | $ITdept = "Your Company Information Technology" 16 | $HelpLink = "https://isotropic.org/papers/chicken.pdf" # Optional - for Readme 17 | $SoftwarecenterShortcut = "softwarecenter:page=OSD" 18 | $AudioSource = "ms-winsoundevent:Notification.Default" 19 | $ShowAvailable = $true 20 | $ShowRequired = $true 21 | 22 | # Base64 strings for populating toast images. Logo should be 48x48, Inline image (ex: IT/Company Logo) should be around 364(or longer)x100 to not display obnoxiously 23 | $Base64LogoImage = "iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAANySURBVGhD7Zg7aBRBGMfjK0qMeia+UgjRQstDMDYSQcSAgmhUEFGDhYhgk1LSaGFAwUIkoIXBgBqC0cqIGCsftY0GQQVRERQt4gOMqHf+/ptvjtO7i5fduYsL+4Mfc/fN7jczu7OvqUlISEjwTjabnY1rcZqFqocaxcXYgptwM67DZVhWh9juOIqtFqosNFSH+/AavsdSqO46HsB6270A6s6i2G+hykADDXgSR9Wa8QNHcAj78QrexMeouoBMJvOZ4gwusXQ5iFV2ACTWNOmgEx/VCuVXil5sw7m2WQHU6UxtwT78htr3E8VRnG6bVXYAJFUndFTFd+zGRqsuG/ZZiufwJ4pbmLK6ygyAhCmO2ANlpnxEsdqqQkMO3W2eW84nFLrY/Q+AZDryD4O02ewNnGNVkSFXCu8psQ3ivH6DnwGQSHP+apByvPMzrMob5NQBuq8GGMRLleBtAB3KZtMm0pFn/1o8hReLeJk2vlA6og+AJA0k1d1GF6yPOb8Ky8XLAHSfF90Wigy51uOOCdQZ34nLbZdwkEDzcpQzoPv8pG+VUw6d1uuB6LVQvKDjercRbRaKD3Rat069fOn9peTrwX8LndYrsRixULyg43qfF0MWihd0XB8jot9CVYe2O/EdNluofNhpJbfPN5SHLVR1aH+Q9kWrheIFAxi2AaQtFC8YwAsbwHwLxQc6rVfsX/jKQvGCjm9H0WeheEHHtSAg2i0UH+h0E45xDXygrLVwOEjQjHvwEO6ewA22S2TI5b6JuywUHpIcHM9VFitst9CQI41aqXiL8ywcHpLkBsAp1WLUJbxQxBMY6VuZ/etp4yml2GXhaJAoGACJ3Ye2PrzrrNob5JxFG7eDFjggFo4OydwZ6KEBLXkILYEstE0iQy4dedf5uxjtws2HZG4AWsfUopPWOHVG9JRssc1CQ440udy0Ueejz/t8SJgbgP3XE1KLtUJPyh5sCjaeBOzjVuDc0qKuI39H3kHSPwYg+K2vtSPoVqW15KKFr21Y8ghStwD1hNW2Yyh0t/FzwRaD5AUDcBBbhKcxf3ldZ+UZ0+IO5QDlIA7blFNdAP/1kOpCv1Pmb2ig5AAc1Gn5ZS8OoI5oUej0awotrbej/+lSDBrStBDHLPRP2LYR1+BGbEU9nKbmlZiGNd/VgZkWSkhISPBFTc1vc9xaMWyYdnsAAAAASUVORK5CYII=" 24 | #InlineImage is optional but looks real nice when you use a company logo 25 | $Base64InlineImage = "" 26 | 27 | <# 28 | #Use this to create a Base64 image string and copy it to the clipboard for pasting into the Base64 variables 29 | $File = "C:\Tools\SCCM Scripts\Chicken.png" 30 | $Image = [System.Drawing.Image]::FromFile($File) 31 | $MemoryStream = New-Object System.IO.MemoryStream 32 | $Image.Save($MemoryStream, $Image.RawFormat) 33 | [System.Byte[]]$Bytes = $MemoryStream.ToArray() 34 | $Base64 = [System.Convert]::ToBase64String($Bytes) 35 | $Image.Dispose() 36 | $MemoryStream.Dispose() 37 | $Base64 | Set-Clipboard 38 | #> 39 | 40 | $softwareCenter = New-Object -ComObject "UIResource.UIResourceMgr" 41 | 42 | IF($ShowAvailable){ 43 | $AvailableTSList = $Softwarecenter.GetAvailableApplications() | Where-Object {$_.IsAssigned -ne 1} 44 | }Else{ 45 | $AvailableTSList = @() 46 | } 47 | 48 | IF($ShowRequired){ 49 | $RequiredTSList = $Softwarecenter.GetAvailableApplications() | Where-Object {$_.IsAssigned -eq 1} 50 | }Else{ 51 | $RequiredTSList = @() 52 | } 53 | 54 | $DeadlineTSList = @() 55 | Foreach($TaskSequence in $RequiredTSList){ 56 | $DeadlineTSList += Get-CimInstance -Query "SELECT * FROM CCM_Program WHERE PackageID='$($TaskSequence.PackageID)' AND ProgramID='*'" -Namespace "root\ccm\clientsdk" 57 | } 58 | 59 | IF($DeadlineTSList){ 60 | $Deadline = ($DeadlineTSList | Sort-Object -Property Deadline -Descending)[0].Deadline.ToUniversalTime() 61 | $TimeSpan = ( $Deadline ) - (get-date) 62 | } 63 | 64 | IF($HelpLink){ 65 | $Actions = @" 66 | 67 | 68 | 69 | "@ 70 | }Else{ 71 | $Actions = @" 72 | 73 | 74 | "@ 75 | } 76 | 77 | # Do nothing if we don't have Task Sequences (just in case) 78 | IF(!$AvailableTSList -and !$RequiredTSList){ 79 | Return 80 | } 81 | 82 | switch ($AvailableTSList.Name.Count) { 83 | {$_ -gt 1} {$TSAvailText = "$($AvailableTSList.Name.Count) Actions"} 84 | {$_ -eq 1} {$TSAvailText = "$($AvailableTSList.Name.Count) Action"} 85 | } 86 | 87 | switch ($RequiredTSList.Name.Count) { 88 | {$_ -gt 1} {$TSReqText = "$($RequiredTSList.Name.Count) Actions"} 89 | {$_ -eq 1} {$TSReqText = "$($RequiredTSList.Name.Count) Action"} 90 | } 91 | 92 | # Set Deadline XML format 93 | IF($TimeSpan -ne $null) { 94 | $Deadline = @" 95 | 96 | 97 | Time Remaining: 98 | 99 | 100 | $($TimeSpan.Days) days $($TimeSpan.Hours) hours $($TimeSpan.Minutes) minutes 101 | 102 | 103 | "@ 104 | }Else{ 105 | $Deadline = $null 106 | } 107 | 108 | # Do some things with the returned Required Task Sequences so they stack for the XML. 109 | IF($RequiredTSList){ 110 | $GroupedRequiredTSList = '' + $TSReqText + ' Required' + '`n' 111 | Foreach($TS in $RequiredTSList){ 112 | $GroupedRequiredTS = @" 113 | $($TS.FullName)`n 114 | "@ 115 | 116 | $GroupedRequiredTSList = $GroupedRequiredTSList + $GroupedRequiredTS 117 | } 118 | }Else{ 119 | $GroupedRequiredTSList = '' 120 | } 121 | 122 | # Do some things with the returned Available Task Sequences so they stack for the XML. 123 | IF($AvailableTSList){ 124 | $GroupedTSList = '' + $TSAvailText + ' Available' + '`n' 125 | Foreach($TS in $AvailableTSList){ 126 | $GroupedTS = @" 127 | $($TS.FullName)`n 128 | "@ 129 | 130 | $GroupedTSList = $GroupedTSList + $GroupedTS 131 | } 132 | }Else{ 133 | $GroupedTSList = '' 134 | } 135 | 136 | # Create an image file from base64 string and save to user temp location 137 | If ($Base64LogoImage) { 138 | $LogoImage = "$env:TEMP\ToastLogo.png" 139 | [byte[]]$Bytes = [convert]::FromBase64String($Base64LogoImage) 140 | [System.IO.File]::WriteAllBytes($LogoImage, $Bytes) 141 | } 142 | 143 | IF($Base64InlineImage) { 144 | $InlineImage = "$env:TEMP\ToastInline.png" 145 | [byte[]]$Bytes = [convert]::FromBase64String($Base64InlineImage) 146 | [System.IO.File]::WriteAllBytes($InlineImage, $Bytes) 147 | } 148 | 149 | # Load some required namespaces 150 | $null = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] 151 | $null = [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] 152 | 153 | # Register the AppID in the registry for use with the Action Center, if required 154 | $RegPath = "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Notifications\Settings" 155 | $App = "Microsoft.SoftwareCenter.DesktopToasts" 156 | # Create registry entries if they don't exist 157 | if (-NOT(Test-Path -Path "$RegPath\$App")) { 158 | New-Item -Path "$RegPath\$App" -Force 159 | New-ItemProperty -Path "$RegPath\$App" -Name "ShowInActionCenter" -Value 1 -PropertyType "DWORD" -Force 160 | New-ItemProperty -Path "$RegPath\$App" -Name "Enabled" -Value 1 -PropertyType "DWORD" -Force 161 | } 162 | # Make sure the app used with the action center is enabled 163 | if ((Get-ItemProperty -Path "$RegPath\$App" -Name "Enabled").Enabled -ne "1") { 164 | New-ItemProperty -Path "$RegPath\$App" -Name "Enabled" -Value 1 -PropertyType "DWORD" -Force 165 | } 166 | 167 | 168 | # Define the toast notification in XML format 169 | [xml]$ToastTemplate = @" 170 | 171 | 172 | 173 | 174 | 175 | $Title 176 | from $ITdept 177 | 178 | 179 | $GroupedRequiredTSList 180 | $GroupedTSList 181 | 182 | 183 | $Deadline 184 | 185 | 186 | 187 | $Actions 188 | 189 | 191 | "@ 192 | 193 | # Load the notification into the required format 194 | $ToastXml = New-Object -TypeName Windows.Data.Xml.Dom.XmlDocument 195 | $ToastXml.LoadXml($ToastTemplate.OuterXml) 196 | 197 | 198 | # Clear old instances of this notification to prevent action center spam 199 | [Windows.UI.Notifications.ToastNotificationManager]::History.Clear($app) 200 | 201 | # Display 202 | [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($app).Show($ToastXml) 203 | -------------------------------------------------------------------------------- /ConfigItems/Windows Update Toasts/README.md: -------------------------------------------------------------------------------- 1 | Update 7/20/2019: Changed AppID on the toast to "Software Center" instead of "Windows Powershell" for a better User Experience 2 | 3 | Problem to solve: 4 | - You deploy updates as available for a period of time to give users some leeway for installation before they're required... but users don't install updates until the deadline has passed. Complaints ensue as reboot timers kick in and force the reboot at an inopportune time for the user. 5 | Disclaimer: this depends a fair bit on how you have your update deployments and maintenance windows configured. 6 | 7 | Here's the default Software Center Notification for required deployments: 8 | 9 | ![logo](https://raw.githubusercontent.com/SysBehr/BehrNecessities/master/Images/DefaultNotifications.png "Software Center Notification") 10 | 11 | Here's the toast notification that this script generates with branding for MMSMOA (with deadline): 12 | 13 | ![logo](https://raw.githubusercontent.com/SysBehr/BehrNecessities/master/Images/Toast_With_Logo.png "Branded Toast Notification") 14 | 15 | Here's the toast notification that this script generates for required deployments (title switches to reboot notification): 16 | 17 | ![logo](https://raw.githubusercontent.com/SysBehr/BehrNecessities/master/Images/Toast_No_Logo.png "Default Toast Notification (no branding)") 18 | 19 | Instructions: 20 | - Test the scripts & modify with your branding. 21 | - Create a CI with the discovery and remediation scripts. Ensure it runs in the user context. 22 | - Compliance is an integer value and it should return 0 to be compliant since it's looking for visible updates (Triggered by the "Display in Software Center" option in the deployment). If you have visible updates, that means we have something available or required. 23 | - I deploy the notification on a schedule of every 8 hours to a computer collection that excludes certain machines (conference rooms/labs/classroom scenarios). Deploy it how it makes sense to in your own environment 24 | 25 | What it does: 26 | - Hooks into the CIM Instance of the CCM client and looks for software updates deployed to the client. It sorts these updates by deadline so we get the earliest deadline and can display a toast notification out to the user for exactly what updates are available or required to install. It works with anything that you set as visible to the user in Software Center (including Feature Updates deployed from SCCM). 27 | - The toast displays all updates to the user, as well as reminds them for restarts after they've taken action to install updates. Clicking "Install Updates" takes them to the Software Center, dismiss dismisses the toast. 28 | 29 | Limitations: 30 | - Toast doesn't go away unless the user clicks on it - In presentation mode, Focus Assist hides these notifications. 31 | - User context GPO deployment flashes a powershell window briefly when launching the toast. Theoretically you can get around this with the runsilent.exe developed by onevinn: https://gallery.technet.microsoft.com/Web-Service-for-OS-93b6ecb8. 32 | 33 | I don't have hard data, but my patch compliance numbers before patch deadlines have improved greatly, and I don't get any more user compliants :) 34 | -------------------------------------------------------------------------------- /ConfigItems/Windows Update Toasts/Toast_Discovery.ps1: -------------------------------------------------------------------------------- 1 | #Discovery Script for SCCM Configuration Item 2 | #If updates are returned (count > 0), display the toast 3 | #Credit goes to @jgkps 4 | 5 | $VisibleUpdates = Get-CimInstance -Namespace Root\ccm\clientSDK -Class CCM_softwareupdate | Where-Object {($_.useruiexperience -eq $true)} 6 | $VisibleUpdates.Name.Count 7 | -------------------------------------------------------------------------------- /ConfigItems/Windows Update Toasts/Toast_Remediation.ps1: -------------------------------------------------------------------------------- 1 | ## Displays a Windows 10 Toast Notification for Windows Updates 2 | ## SCCM Configuration Item Remediation Script 3 | 4 | ## References 5 | # Source Script/Idea: https://smsagent.wordpress.com/2018/06/15/using-windows-10-toast-notifications-with-configmgr-application-deployments/ 6 | # Options for audio: https://docs.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-audio#attributes-and-elements 7 | # Toast content schema: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/toast-schema 8 | # 9 | # Author: Colin Wilkins @SysBehr 10 | # Assistance from @jgkps on the Windows Admins Discord 11 | # Modified: 03/13/2020 12 | # Version: 3.8 13 | 14 | # Required parameters 15 | $Title = "Updates are ready to install" 16 | $ITdept = "SysBehr Information Technology" 17 | $SoftwarecenterShortcut = "softwarecenter:page=updates" 18 | $AudioSource = "ms-winsoundevent:Notification.Default" 19 | 20 | $LightTheme = (Get-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize -Name SystemUsesLightTheme).SystemUsesLightTheme 21 | 22 | # Light theme should use dark logo files, Dark theme should use light logo files 23 | IF($LightTheme){ 24 | # Base64 strings for populating toast images. Logo should be 48x48, Inline image (ex: IT/Company Logo) should be around 364(or longer)x100 to not display obnoxiously 25 | $Base64LogoImage = "iVBORw0KGgoAAAANSUhEUgAAAEUAAABFCAYAAAAcjSspAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwgAADsIBFShKgAAABd9JREFUeF7tm0moHGUUheO0UANqBqJiwkuUYIJBxGGnKCQhIoQs1I1m4YAg6EYUdOEQERHciIILEUcEV8ExkoXggBM4EVEkLhRRnBcOiMahPae553H936nu18/q6lddHvgg7/x/3fvfmx6qq/5a0uv1/qfAml3Hml3Hml3Hml3Hml3Hml3HmqJuzczMLAXngJvBE+BtsA/8AXrgu/ib/mPgRrAVLIsQtcnVK6wp6hAKYiOuBHvBr4DFj8rv4GVwPVgVoYcKc8nl4EVwSdh9uXqFNcV/ERaxDtwPfgSu0IXyG3gcnB6pKoU5J8cx5G+wOoZsvcKaYiFC4jXgIaC3xCC+Ap+CPQn+Td/NL3kGnBip5whjfJXk+RtiyNYrrClGERIeCm4AVa8MNuk1sAvwc2IFODgO/5fox/h2wPnvgKomHwC3g8Pj8FnBuyLmiOaagmSrwEspeeYbcCtYE9MXJB4fcT4HLs/7YH1M7wt/l005P4ZsvcKaYj5CojMBvzVycsK3AD9gD4mptYjxwE7wJShzch1bYqprysUxZOsV1hTDhCQ7AL8ZcuK/wH1gaUwbixg/8jBfzs/17Ig5ZVMu7B8MuXqFNcUgIYFryA9ga0xpRMwHyldNvzGguc8UBD8LlA35EJwUUxoV8q4Fb8Y6BNf3VOGNpykIfDwoP0PeAkfFlImI+WMdeV0l9TcFQfkhxzPLnOgjcHRMmai4DvBGrMsxlqbcVCT5HqyL4UUhrOc48EWsr6TepiAg9RNQAn7qb47hxoSc1wKeq/D8pwr+FMjNELU35ZEiwb0x1JiQcyX4M61hVOprCoJtAnkxPDFbHsONCTmXgYX+0uYPwtmzalevsKaQEOzRFJxcFkONC7l5PWY34DWX+fI0uDRC9OXqFdYUFILxd80vQA35DNR66j4JuXqFNQWFBlydGkJu6Q+0XK5eYU1BoQmvp4bwc+WE/kDL5eoV1hRowHKQT+dfjZitl6tXWFOgCRekhpBdEbP1cvUKawo04a6iKY3+Ah6nXL3CmgJN4G2I3JSVEbP1cvUKawo04b3UkK/BQRGz9XL1CmsKNGF/asq7Ea+VwvrJKYAXnw5z9QprChyshpC9Eb+Vwvo3p1qedPUKawocnM9kd0f8Vgrr570o1YLyfM3EmgIHP5ACzV70baOwft62raUpZAs4g/9us7D+3JQDrl5hTTFNQiM+SE3Z5+oV1hTTIjThSPBzasorrl5hTTEtQhM2pIaQe1y9wppiWoQmXFU0ZaerV1hTTIvQhOdSQ3j5Y72rV1hTTIPQgGMAt2uoKfvpu3qFNcU0CE24LjWE3EHf1SusKdouNIB3ND9ODeErZi3HXL3CmiILwfhD6uEEb167K+clEzsTRu6LQH6V7IkhW6+wppAQjFf0y10G84X/O43vRkBOnpt8EmsgvO9zbgzbeoU1hYRg5ff8qJwaoRoTcnIfXF7DCzHUl6tXWFNICDioKd8Cdz9XcJ9aoxenkO80kO9o8tW6KYb7cvUKawoJAauawlPnyi2bkxDWw69g3rDL67wzhmfl6hXWFBKCDnqlLPb9KdzIc0RMmZWrV1hTSAg67DNlse5k4v4Ze83D1SusKSTELZvCr+PFtOeNG3XcnrftMWWOXL3CmkJC8LIp3HVYtTuyciHjEPKdB8oNx1xXf9tolVy9wppCQoI5TQnfNUb7aFf0Dx6TEJ/7aO+OfDn/0IZQrl5hTSEhiW0KhX+fDap2XF8DJrbjepBcvcKaQkKiyqZQ+JtnvOV7WvClXcfe/GMjTtXefOafdw5Xr7CmkJBsYFMoeON4ioPzOJ/HlW9Twdswt4E5T3EMkqtXWFNISDi0KRLGmnze51mwMVKPJFevsKaQkHg1yNswt8VQpTBnI3gQ5AvGdcB1sBlDnwwbJFevsKbIwiK2gecBn7YKd7gwl28DPUNY9RYYBo8b+RnCQXL1CmuKuoWCuOVz0NOmfFUt7qdNu4o1u441u441u441u441u441u01vyT9QACl0M/yngQAAAABJRU5ErkJggg==" 26 | #InlineImage is optional but looks real nice when you use a company logo 27 | $Base64InlineImage = "" 28 | }Else{ 29 | # Base64 strings for populating toast images. Logo should be 48x48, Inline image (ex: IT/Company Logo) should be around 364(or longer)x100 to not display obnoxiously 30 | $Base64LogoImage = "iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAANySURBVGhD7Zg7aBRBGMfjK0qMeia+UgjRQstDMDYSQcSAgmhUEFGDhYhgk1LSaGFAwUIkoIXBgBqC0cqIGCsftY0GQQVRERQt4gOMqHf+/ptvjtO7i5fduYsL+4Mfc/fN7jczu7OvqUlISEjwTjabnY1rcZqFqocaxcXYgptwM67DZVhWh9juOIqtFqosNFSH+/AavsdSqO46HsB6270A6s6i2G+hykADDXgSR9Wa8QNHcAj78QrexMeouoBMJvOZ4gwusXQ5iFV2ACTWNOmgEx/VCuVXil5sw7m2WQHU6UxtwT78htr3E8VRnG6bVXYAJFUndFTFd+zGRqsuG/ZZiufwJ4pbmLK6ygyAhCmO2ANlpnxEsdqqQkMO3W2eW84nFLrY/Q+AZDryD4O02ewNnGNVkSFXCu8psQ3ivH6DnwGQSHP+apByvPMzrMob5NQBuq8GGMRLleBtAB3KZtMm0pFn/1o8hReLeJk2vlA6og+AJA0k1d1GF6yPOb8Ky8XLAHSfF90Wigy51uOOCdQZ34nLbZdwkEDzcpQzoPv8pG+VUw6d1uuB6LVQvKDjercRbRaKD3Rat069fOn9peTrwX8LndYrsRixULyg43qfF0MWihd0XB8jot9CVYe2O/EdNluofNhpJbfPN5SHLVR1aH+Q9kWrheIFAxi2AaQtFC8YwAsbwHwLxQc6rVfsX/jKQvGCjm9H0WeheEHHtSAg2i0UH+h0E45xDXygrLVwOEjQjHvwEO6ewA22S2TI5b6JuywUHpIcHM9VFitst9CQI41aqXiL8ywcHpLkBsAp1WLUJbxQxBMY6VuZ/etp4yml2GXhaJAoGACJ3Ye2PrzrrNob5JxFG7eDFjggFo4OydwZ6KEBLXkILYEstE0iQy4dedf5uxjtws2HZG4AWsfUopPWOHVG9JRssc1CQ440udy0Ueejz/t8SJgbgP3XE1KLtUJPyh5sCjaeBOzjVuDc0qKuI39H3kHSPwYg+K2vtSPoVqW15KKFr21Y8ghStwD1hNW2Yyh0t/FzwRaD5AUDcBBbhKcxf3ldZ+UZ0+IO5QDlIA7blFNdAP/1kOpCv1Pmb2ig5AAc1Gn5ZS8OoI5oUej0awotrbej/+lSDBrStBDHLPRP2LYR1+BGbEU9nKbmlZiGNd/VgZkWSkhISPBFTc1vc9xaMWyYdnsAAAAASUVORK5CYII=" 31 | #InlineImage is optional but looks real nice when you use a company logo 32 | $Base64InlineImage = "" 33 | } 34 | 35 | <# 36 | #Use this to create a Base64 image string and copy it to the clipboard for pasting into the Base64 variables 37 | $File = "C:\image.png" 38 | $Image = [System.Drawing.Image]::FromFile($File) 39 | $MemoryStream = New-Object System.IO.MemoryStream 40 | $Image.Save($MemoryStream, $Image.RawFormat) 41 | [System.Byte[]]$Bytes = $MemoryStream.ToArray() 42 | $Base64 = [System.Convert]::ToBase64String($Bytes) 43 | $Image.Dispose() 44 | $MemoryStream.Dispose() 45 | $Base64 | Set-Clipboard 46 | #> 47 | 48 | # list all visible updates currently available for installation on the system, sorted by earliest deadline 49 | $DeadlinedUpdates = @(Get-CimInstance -Namespace Root\ccm\clientSDK -Class CCM_softwareupdate | Where-Object {($_.useruiexperience -eq $true) -and ($_.deadline)} | Sort-Object -Property Deadline) 50 | $AvailableUpdates = @(Get-CimInstance -Namespace Root\ccm\clientSDK -Class CCM_softwareupdate | Where-Object {($_.useruiexperience -eq $true) -and !($_.deadline)}) 51 | 52 | # Get Pending Reboot status 53 | $rebootinfo = Invoke-CimMethod -ClassName CCM_ClientUtilities -Namespace root\ccm\clientsdk -MethodName DetermineIfRebootPending 54 | 55 | # Do nothing if we don't have updates (just in case) 56 | IF(!$DeadlinedUpdates -and !$AvailableUpdates){ 57 | Return 58 | } 59 | 60 | # Get deadlines 61 | If($DeadlinedUpdates[0].deadline){ 62 | 63 | $updateDeadline = $DeadlinedUpdates[0].deadline 64 | 65 | # if the deadline specified by the update has passed, set $DeadLinePassed to $true 66 | if ( ($updateDeadline ) -lt (get-date) ) { 67 | $DeadLinePassed = $true 68 | $ShowDeadline = $false 69 | } 70 | elseif ( ( $updateDeadline ) -gt (get-date) ) { 71 | 72 | $TimeSpan = ( $updateDeadline ) - (get-date) 73 | $ShowDeadline = $true 74 | } 75 | } 76 | 77 | # Grammar is important 78 | switch ($DeadlinedUpdates.Name.Count) { 79 | {$_ -gt 1} {$UpdatesText = "$($DeadlinedUpdates.Name.Count) Updates"} 80 | {$_ -eq 1} {$UpdatesText = "$($DeadlinedUpdates.Name.Count) Update"} 81 | } 82 | 83 | switch ($AvailableUpdates.Name.Count) { 84 | {$_ -gt 1} {$UpdatesAvailText = "$($AvailableUpdates.Name.Count) Updates"} 85 | {$_ -eq 1} {$UpdatesAvailText = "$($AvailableUpdates.Name.Count) Update"} 86 | } 87 | 88 | 89 | # if deadline has passed, update toast wording and calculate deadline timespan 90 | If ($DeadlinePassed -and $DeadlinedUpdates) { 91 | $TimeSpan = $updateDeadline - $updateDeadline 92 | $AudioSource = "ms-winsoundevent:Notification.Reminder" 93 | }Else{ 94 | } 95 | 96 | IF($rebootinfo.RebootPending){ 97 | $rebootDeadline = $rebootinfo.RebootDeadline 98 | $Title = "Updates require a system restart" 99 | switch ($DeadlinedUpdates.Name.Count) { 100 | {$_ -gt 1} {$Status = "require a restart:"} 101 | {$_ -eq 1} {$Status = "requires a restart:"} 102 | } 103 | $TimeSpan = $rebootDeadline - (Get-Date) 104 | $showDeadline = $false 105 | } 106 | 107 | $Deadline = '' 108 | IF($showDeadline){ 109 | $Deadline = @" 110 | 111 | 112 | Time Remaining: 113 | 114 | 115 | $($TimeSpan.Days) days $($TimeSpan.Hours) hours $($TimeSpan.Minutes) minutes 116 | 117 | 118 | "@ 119 | } 120 | 121 | # Do some things with the returned updates so they stack for the XML. More than 13 updates breaks the toast, so truncate at 12. 122 | IF($DeadlinedUpdates){ 123 | $GroupedDeadlinedUpdates = '' + $UpdatesText + ' Required' + '`n' 124 | Foreach($Update in $DeadlinedUpdates[0..9]){ 125 | $GroupedUpdate = @" 126 | $($update.name.replace(',',''))`n 127 | "@ 128 | 129 | $GroupedDeadlinedUpdates = $GroupedDeadlinedUpdates + $GroupedUpdate 130 | } 131 | }Else{ 132 | $GroupedDeadlinedUpdates = '' 133 | } 134 | 135 | IF($AvailableUpdates){ 136 | $GroupedAvailableUpdates = '' + $UpdatesAvailText + ' Available' + '`n' 137 | Foreach($Update in $AvailableUpdates[0..2]){ 138 | $GroupedUpdate = @" 139 | $($update.name.replace(',',''))`n 140 | "@ 141 | 142 | $GroupedAvailableUpdates = $GroupedAvailableUpdates + $GroupedUpdate 143 | } 144 | }Else{ 145 | $GroupedAvailableUpdates = '' 146 | } 147 | 148 | # Create an image file from base64 string and save to user temp location 149 | If ($Base64LogoImage) { 150 | $LogoImage = "$env:TEMP\ToastLogo.png" 151 | [byte[]]$Bytes = [convert]::FromBase64String($Base64LogoImage) 152 | [System.IO.File]::WriteAllBytes($LogoImage, $Bytes) 153 | } 154 | 155 | IF($Base64InlineImage) { 156 | $InlineImage = "$env:TEMP\ToastInline.png" 157 | [byte[]]$Bytes = [convert]::FromBase64String($Base64InlineImage) 158 | [System.IO.File]::WriteAllBytes($InlineImage, $Bytes) 159 | } 160 | 161 | # Load some required namespaces 162 | $null = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] 163 | $null = [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] 164 | 165 | # Register the AppID in the registry for use with the Action Center, if required 166 | $RegPath = "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Notifications\Settings" 167 | $App = "Microsoft.SoftwareCenter.DesktopToasts" 168 | # Create registry entries if they don't exist 169 | if (-NOT(Test-Path -Path "$RegPath\$App")) { 170 | New-Item -Path "$RegPath\$App" -Force 171 | New-ItemProperty -Path "$RegPath\$App" -Name "ShowInActionCenter" -Value 1 -PropertyType "DWORD" -Force 172 | New-ItemProperty -Path "$RegPath\$App" -Name "Enabled" -Value 1 -PropertyType "DWORD" -Force 173 | } 174 | # Make sure the app used with the action center is enabled 175 | if ((Get-ItemProperty -Path "$RegPath\$App" -Name "Enabled").Enabled -ne "1") { 176 | New-ItemProperty -Path "$RegPath\$App" -Name "Enabled" -Value 1 -PropertyType "DWORD" -Force 177 | } 178 | 179 | # Define the toast notification in XML format 180 | [xml]$ToastTemplate = @" 181 | 182 | 183 | 184 | 185 | 186 | $Title 187 | from $ITdept 188 | 189 | 190 | $GroupedAvailableUpdates 191 | $GroupedDeadlinedUpdates 192 | 193 | 194 | $Deadline 195 | 196 | 197 | 198 | 199 | 200 | 201 | 203 | "@ 204 | 205 | # Load the notification into the required format 206 | $ToastXml = New-Object -TypeName Windows.Data.Xml.Dom.XmlDocument 207 | $ToastXml.LoadXml($ToastTemplate.OuterXml) 208 | 209 | 210 | # Clear old instances of this notification to prevent action center spam 211 | [Windows.UI.Notifications.ToastNotificationManager]::History.Clear($app) 212 | 213 | # Display 214 | [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($app).Show($ToastXml) 215 | -------------------------------------------------------------------------------- /FrontEnds/BIOSServicingGUI/BIOSServicingGUI.ps1: -------------------------------------------------------------------------------- 1 | PlaceHolder 2 | -------------------------------------------------------------------------------- /FrontEnds/OSDFrontEnd/FrontEnd.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 12 | 13 | 14 | 15 |