├── .vs ├── ProjectSettings.json ├── slnx.sqlite ├── BranchCache │ └── v15 │ │ └── .suo └── VSWorkspaceState.json ├── README.md ├── CacheInjection ├── .vs │ ├── ProjectSettings.json │ ├── slnx.sqlite │ └── CacheInjection │ │ └── v15 │ │ └── .suo └── InjectBCData.ps1 ├── BCMon ├── BCMon-1.7.0.2504.1.zip └── TwoPint.BCMon.Framework_Release_x64_1.27.0.0.zip ├── GPO Backup - BranchCache Firewall └── BranchCache Firewall Settings - GPO Backup.zip ├── ConfigMgr Configuration Item (CI) to Enable and Tune BranchCache ├── Import File │ ├── Branchcache Tuner 1.0.1.3.cab │ └── Branchcache Tuner 1.0.1.4.zip └── Source │ ├── CacheSize_REMEDIATE.ps1 │ ├── CacheSize_DISCOVERY.ps1 │ ├── MAIN_REMEDIATE.ps1 │ └── MAIN_DISCOVERY.ps1 ├── Clear ConfigMgr and BranchCache caches └── ClearBranchCacheAndConfigMgrCaches.ps1 ├── Verify BranchCache Flags on Packages, Software Updates, Applications and Deployments ├── Sample Commands - Set-BranchCache-SoftwareUpdates.ps1 ├── Sample Commands - Set-BranchCache-Apps.ps1 ├── Sample Commands - Set-BranchCache-Packages.ps1 ├── Set-BranchCache-TaskSequences.ps1 ├── Set-BranchCache-SoftwareUpdates.ps1 ├── Set-BranchCache-Apps.ps1 └── Set-BranchCache-Packages.ps1 ├── Creating P2P Test Packages ├── Get-BITSInfoFromEventLog.ps1 └── New-SetOfP2PTestPackages.ps1 ├── LICENSE ├── Event ├── Get-BCDataCacheReconfigureFromEventLog.ps1 └── CheckBCEvents.ps1 ├── Large-Scale BCMon Testing ├── New-BCMonReport.ps1 ├── Invoke-BCMonProbeMatch.ps1 ├── Invoke-BCMonProbeV2.ps1 └── Invoke-MassBCMonProbing.ps1 ├── Troubleshooting └── Test-BitsDownload.ps1 └── MigrationFromLegacyP2P └── AdaptivaBranchCache.ps1 /.vs/ProjectSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "CurrentProjectSetting": null 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BranchCache 2 | Scripts and tools for managing BranchCache 3 | -------------------------------------------------------------------------------- /CacheInjection/.vs/ProjectSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "CurrentProjectSetting": null 3 | } -------------------------------------------------------------------------------- /.vs/slnx.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2pintsoftware/BranchCache/HEAD/.vs/slnx.sqlite -------------------------------------------------------------------------------- /.vs/BranchCache/v15/.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2pintsoftware/BranchCache/HEAD/.vs/BranchCache/v15/.suo -------------------------------------------------------------------------------- /BCMon/BCMon-1.7.0.2504.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2pintsoftware/BranchCache/HEAD/BCMon/BCMon-1.7.0.2504.1.zip -------------------------------------------------------------------------------- /CacheInjection/.vs/slnx.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2pintsoftware/BranchCache/HEAD/CacheInjection/.vs/slnx.sqlite -------------------------------------------------------------------------------- /CacheInjection/.vs/CacheInjection/v15/.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2pintsoftware/BranchCache/HEAD/CacheInjection/.vs/CacheInjection/v15/.suo -------------------------------------------------------------------------------- /BCMon/TwoPint.BCMon.Framework_Release_x64_1.27.0.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2pintsoftware/BranchCache/HEAD/BCMon/TwoPint.BCMon.Framework_Release_x64_1.27.0.0.zip -------------------------------------------------------------------------------- /GPO Backup - BranchCache Firewall/BranchCache Firewall Settings - GPO Backup.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2pintsoftware/BranchCache/HEAD/GPO Backup - BranchCache Firewall/BranchCache Firewall Settings - GPO Backup.zip -------------------------------------------------------------------------------- /ConfigMgr Configuration Item (CI) to Enable and Tune BranchCache/Import File/Branchcache Tuner 1.0.1.3.cab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2pintsoftware/BranchCache/HEAD/ConfigMgr Configuration Item (CI) to Enable and Tune BranchCache/Import File/Branchcache Tuner 1.0.1.3.cab -------------------------------------------------------------------------------- /ConfigMgr Configuration Item (CI) to Enable and Tune BranchCache/Import File/Branchcache Tuner 1.0.1.4.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2pintsoftware/BranchCache/HEAD/ConfigMgr Configuration Item (CI) to Enable and Tune BranchCache/Import File/Branchcache Tuner 1.0.1.4.zip -------------------------------------------------------------------------------- /.vs/VSWorkspaceState.json: -------------------------------------------------------------------------------- 1 | { 2 | "ExpandedNodes": [ 3 | "", 4 | "\\ConfigMgr Configuration Item (CI) to Enable and Tune BranchCache\\Import File", 5 | "\\ConfigMgr Configuration Item (CI) to Enable and Tune BranchCache\\Source", 6 | "\\MigrationFromLegacyP2P" 7 | ], 8 | "SelectedNode": "\\MigrationFromLegacyP2P\\AdaptivaBranchCache.ps1", 9 | "PreviewInSolutionExplorer": false 10 | } -------------------------------------------------------------------------------- /Clear ConfigMgr and BranchCache caches/ClearBranchCacheAndConfigMgrCaches.ps1: -------------------------------------------------------------------------------- 1 | # Clear ConfigMgr Cache 2 | $UIResourceMgr = New-Object -ComObject UIResource.UIResourceMgr 3 | $Cache = $UIResourceMgr.GetCacheInfo() 4 | $CacheElements = $Cache.GetCacheElements() 5 | foreach ($Element in $CacheElements) { $Cache.DeleteCacheElementEx($Element.CacheElementID, $true) } 6 | 7 | # Clear BranchCache cache 8 | Clear-BCCache -Force -------------------------------------------------------------------------------- /Verify BranchCache Flags on Packages, Software Updates, Applications and Deployments/Sample Commands - Set-BranchCache-SoftwareUpdates.ps1: -------------------------------------------------------------------------------- 1 | # Gather info about all software updates 2 | .\Set-BranchCache-SoftwareUpdates.ps1 -SiteServer CM01 -Mode Gather 3 | 4 | # Disable BranchCache on all software updates 5 | .\Set-BranchCache-SoftwareUpdates.ps1 -SiteServer CM01 -Mode Disable 6 | 7 | # Enable BranchCache on all software updates 8 | .\Set-BranchCache-SoftwareUpdates.ps1 -SiteServer CM01 -Mode Enable 9 | -------------------------------------------------------------------------------- /Creating P2P Test Packages/Get-BITSInfoFromEventLog.ps1: -------------------------------------------------------------------------------- 1 | # Check P2P efficiency for the last three downloaded file via the Event Log 2 | $Events = Get-WinEvent -FilterHashTable @{ LogName="*Bits*"; ID=60} | foreach { 3 | $_ | Add-Member -MemberType NoteProperty -Name name -Value $_.Properties[1].Value; 4 | $_ | Add-Member -MemberType NoteProperty -Name url -Value $_.Properties[3].Value; 5 | $_ | Add-Member -MemberType NoteProperty -Name bytesTotal -Value $_.Properties[8].Value; 6 | $_ | Add-Member -MemberType NoteProperty -Name bytesTransferred -Value $_.Properties[9].Value; 7 | $_ | Add-Member -MemberType NoteProperty -Name bytesTransferredFromPeer -Value $_.Properties[12].Value -PassThru; 8 | } 9 | $events | Sort-Object TimeCreated -Descending | Select -First 3 TimeCreated, url, bytesTotal, bytesTransferred, bytesTransferredFromPeer 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 2Pint Software 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Verify BranchCache Flags on Packages, Software Updates, Applications and Deployments/Sample Commands - Set-BranchCache-Apps.ps1: -------------------------------------------------------------------------------- 1 | # Gather info about all apps 2 | .\Set-BranchCache-Apps.ps1 -SiteServer CM01 -Mode Gather 3 | 4 | # Disable BranchCache on all apps that have content 5 | .\Set-BranchCache-Apps.ps1 -SiteServer CM01 -Mode Disable 6 | 7 | # Enable BranchCache on all apps that have content 8 | .\Set-BranchCache-Apps.ps1 -SiteServer CM01 -Mode Enable 9 | 10 | 11 | # Gather info about individual app(s), wild cards supported 12 | .\Set-BranchCache-Apps.ps1 -SiteServer CM01 -Mode Gather -AppName "Microsoft Office 2016" 13 | .\Set-BranchCache-Apps.ps1 -SiteServer CM01 -Mode Gather -AppName "*Office*" 14 | 15 | # Disable BranchCache on individual app(s), wild cards supported 16 | .\Set-BranchCache-Apps.ps1 -SiteServer CM01 -Mode Disable -AppName "Microsoft Office 2016" 17 | .\Set-BranchCache-Apps.ps1 -SiteServer CM01 -Mode Disable -AppName "*Office*" 18 | 19 | # Enable BranchCache on individual app(s), wild cards supported 20 | .\Set-BranchCache-Apps.ps1 -SiteServer CM01 -Mode Enable -AppName "Microsoft Office 2016" 21 | .\Set-BranchCache-Apps.ps1 -SiteServer CM01 -Mode Enable -AppName "*Office*" 22 | -------------------------------------------------------------------------------- /Verify BranchCache Flags on Packages, Software Updates, Applications and Deployments/Sample Commands - Set-BranchCache-Packages.ps1: -------------------------------------------------------------------------------- 1 | # Gather info about all packages 2 | .\Set-BranchCache-Packages.ps1 -SiteServer CM01 -Mode Gather 3 | 4 | # Disable BranchCache on all packages that have content 5 | .\Set-BranchCache-Packages.ps1 -SiteServer CM01 -Mode Disable 6 | 7 | # Enable BranchCache on all packages that have content 8 | .\Set-BranchCache-Packages.ps1 -SiteServer CM01 -Mode Enable 9 | 10 | 11 | # Gather info about individual package(s), wild cards supported 12 | .\Set-BranchCache-Packages.ps1 -SiteServer CM01 -Mode Gather -PackageName "1 GB File 001" 13 | .\Set-BranchCache-Packages.ps1 -SiteServer CM01 -Mode Gather -PackageName "*File*" 14 | 15 | # Disable BranchCache on individual package(s), wild cards supported 16 | .\Set-BranchCache-Packages.ps1 -SiteServer CM01 -Mode Disable -PackageName "1 GB File 001" 17 | .\Set-BranchCache-Packages.ps1 -SiteServer CM01 -Mode Disable -PackageName "*File*" 18 | 19 | # Enable BranchCache on individual package(s), wild cards supported 20 | .\Set-BranchCache-Packages.ps1 -SiteServer CM01 -Mode Enable -PackageName "1 GB File 001" 21 | .\Set-BranchCache-Packages.ps1 -SiteServer CM01 -Mode Enable -PackageName "*File*" -------------------------------------------------------------------------------- /Event/Get-BCDataCacheReconfigureFromEventLog.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Get-BCDataCacheReconfigureFromEventLog.ps1 4 | 5 | .DESCRIPTION 6 | Enumerates all items in the eventlog under Microsoft-Windows-BranchCache/Operational with ID 5 to find if the BranchCache DataCache size have been changed 7 | 8 | .NOTES 9 | Version: 1.0 10 | Author: MB @ 2Pint Software 11 | Creation Date: 2023-08-24 12 | Purpose/Change: Initial script development 13 | 14 | .LINK 15 | https://2pintsoftware.com 16 | 17 | #> 18 | 19 | $startdate = (Get-date).AddDays(-30) 20 | $events = Get-WinEvent -FilterHashtable @{ Logname='Microsoft-Windows-BranchCache/Operational'; ID=5; StartTime=$startdate} 21 | 22 | 23 | foreach($event in $events) 24 | { 25 | [xml]$XMLEvent = $event.ToXml() 26 | if($XMLEvent.Event.UserData.ConfigChangeEvent) 27 | { 28 | $ConfigChangeEvent = $XMLEvent.Event.UserData.ConfigChangeEvent 29 | if($ConfigChangeEvent.SubKey -eq "CacheMgr\Republication") 30 | { 31 | $timeStamp = "{0:yyyy-MM-dd HH:mm:ss}" -f [dateTime]$XMLEvent.Event.System.TimeCreated.SystemTime 32 | Write-host "$timeStamp : $($ConfigChangeEvent.Subkey) setting $($ConfigChangeEvent.ValueName) was changed to $($ConfigChangeEvent.UInt32)" 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /Large-Scale BCMon Testing/New-BCMonReport.ps1: -------------------------------------------------------------------------------- 1 | $HealthCheckPath = "\\CM01\HealthCheck$" 2 | $BCMonResultsPath = "$HealthCheckPath\BCMonResults" 3 | $Files = Get-ChildItem -Path $BCMonResultsPath -Filter "*.txt" -Recurse | Select-Object -ExpandProperty FullName 4 | 5 | $Result = foreach ($File in $Files) { 6 | $FileContent = Get-Content -Path $File 7 | 8 | $ConnectionType = $FileContent | Select-String -SimpleMatch -Pattern "Connection Type" 9 | $ConnectionType = $ConnectionType -split (":",2) | select -last 1 10 | 11 | $SearchString2 = "Unicast BranchCache" 12 | if ($FileContent -match $SearchString2 ) { 13 | $IP = Split-Path (Split-Path "$File" -Parent) -Leaf 14 | $SEP = $IP.lastindexof(".") 15 | $subnet = "$($ip.substring(0,$sep)).0" 16 | Write-Output "$subnet, $IP, OK, $SearchString2,$ConnectionType" 17 | } 18 | Else{ 19 | $SearchString3 = "Incoming 2Pint PeerDist" 20 | if ($FileContent -match $SearchString3 ){ 21 | $IP = Split-Path (Split-Path "$File" -Parent) -Leaf 22 | $SEP = $IP.lastindexof(".") 23 | $subnet = "$($ip.substring(0,$sep)).0" 24 | Write-Output "$subnet, $IP, OK, $SearchString3,$ConnectionType" 25 | } 26 | Else{ 27 | $SEP = $IP.lastindexof(".") 28 | $subnet = "$($ip.substring(0,$sep)).0" 29 | Write-Output "$subnet, $IP, NOT OK, NA,$ConnectionType" 30 | } 31 | 32 | } 33 | } 34 | 35 | $Result | Out-File "$HealthCheckPath\BCMonSummaryReport.csv" 36 | 37 | -------------------------------------------------------------------------------- /Large-Scale BCMon Testing/Invoke-BCMonProbeMatch.ps1: -------------------------------------------------------------------------------- 1 | $HealthCheckPath = "\\CM01\HealthCheck$" 2 | $BCMonResultPath = "$HealthCheckPath\BCMonResults" 3 | $ProbeType = "/ProbeMatch" 4 | $ProbeLogfile = "C:\Windows\temp\BCMon-ProbeTest.txt" 5 | 6 | #Check Architecture for BCMon 7 | if ([System.Environment]::Is64BitProcess){ 8 | $Architecture = "x64" 9 | } else { 10 | $Architecture = "x86" 11 | } 12 | $BCMonPath = "$HealthCheckPath\BCMon\$Architecture\bcmon.exe" 13 | 14 | If(test-path C:\Windows\temp\BCMon-ProbeTest.txt){ 15 | remove-item C:\Windows\temp\BCMon-ProbeTest.txt -force 16 | } 17 | 18 | $Process = Get-Process bcmon -ErrorAction SilentlyContinue 19 | If($Process){ 20 | stop-process -name bcmon -force 21 | } 22 | 23 | # Get IPv4 address only 24 | $IPv4 = Get-WmiObject win32_networkadapterconfiguration | Where-Object { $_.IPEnabled -eq $true } | Select -ExpandProperty ipaddress | Select -First 1 25 | 26 | # Copy the BCMon.exe utility 27 | Copy-Item -Path $BCMonPath -Destination "C:\Windows\Temp" 28 | 29 | # Delete any existing Firewall rule for BCMon 30 | netsh advfirewall firewall delete rule name="2Pint Software BCMon" 31 | 32 | # Create Firewall rule to allow BCMon to run 33 | netsh advfirewall firewall add rule name="2Pint Software BCMon" dir=in action=allow program="C:\Windows\Temp\BCMon.exe" enable=yes 34 | 35 | $Cmd = "C:\Windows\System32\cmd.exe" 36 | 37 | If(Test-path -Path $ProbeLogfile){Remove-Item $ProbeLogfile -Force } 38 | $Arglist = "/C C:\Windows\temp\BCMon.exe $ProbeType $IPv4 >> $ProbeLogfile" 39 | Start-Process -Filepath $Cmd -Argumentlist $Arglist -NoNewWindow 40 | 41 | # Allow probes 10 minutes to connect 42 | start-sleep 600 43 | 44 | xcopy $ProbeLogfile "$BCMonResultPath\$IPv4\" /y/i 45 | $Process = Get-Process bcmon -ErrorAction SilentlyContinue 46 | If($Process) 47 | { 48 | stop-process -name bcmon -force 49 | } -------------------------------------------------------------------------------- /Event/CheckBCEvents.ps1: -------------------------------------------------------------------------------- 1 | #pulls all the event 13 from the BC event log and spits out the URL of the guilty content 2 | #then tries to verify the CI using BCMon 3 | 4 | 5 | if(!$PSScriptRoot){ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent } 6 | Function TimeStamp {$(Get-Date -UFormat "%D %T")} 7 | $Logfile = "$PSScriptRoot\BCEventChecker.log" 8 | #delete any existing logfile if it exists 9 | If (Test-Path $Logfile){ri $Logfile -Force -ErrorAction SilentlyContinue -Confirm:$false} 10 | 11 | #Query the BranchCache Event log 12 | 13 | $AllEntries = try { 14 | Get-WinEvent -LogName "Microsoft-Windows-BranchCache/Operational" | Where-Object {$_.ID -eq 13} 15 | } 16 | catch [Exception] { 17 | if ($_.Exception -match "There is not an event log") { 18 | $(TimeStamp) + " No BranchCache Event Log found, exiting" | Out-File -FilePath $Logfile -Append -Encoding ascii; 19 | Exit 0 20 | } 21 | } 22 | 23 | If (!$AllEntries){$(TimeStamp) + " No BranchCache Event ID 13 found, exiting" | Out-File -FilePath $Logfile -Append -Encoding ascii; 24 | Exit 0 25 | } 26 | 27 | 28 | Foreach ($evt in $AllEntries){ 29 | $event = [xml]$evt.ToXml() 30 | $url = $event.Event.UserData.PublishFailedEvent.ContentId 31 | #create the URL from the hex content ID 32 | $id = -join ( 33 | 34 | $url | Select-String ".." -AllMatches | 35 | 36 | ForEach-Object Matches | 37 | 38 | ForEach-Object { 39 | If ([string]$_ -eq "00") {} 40 | Else{[char]+"0x$_"} 41 | } 42 | ) 43 | 44 | #then check with BCMon 45 | $(TimeStamp) + " : Checking URL - " + $id | Out-File $Logfile -Append 46 | 47 | $BCMonCommand = {.\BCMon.exe /VerifyCI $id} 48 | $BCMonResult = Invoke-Command -ScriptBlock $BCMonCommand 49 | 50 | $(TimeStamp) + " : BCMon Returned - " + $BCMonResult | Out-File $Logfile -Append 51 | -------------------------------------------------------------------------------- /Troubleshooting/Test-BitsDownload.ps1: -------------------------------------------------------------------------------- 1 | $DownloadPath = "C:\BCTemp" 2 | New-Item -Path $DownloadPath -ItemType Directory -Force 3 | 4 | $Cred = Get-Credential 5 | $URL = "http://dp01.corp.viamonstra.com:80/SMS_DP_SMSPKG$/PS1001EB/sccm?/10GB-W11-X64-22H2-Enterprise.wim" 6 | 7 | # Download the file 8 | $Job = Start-BitsTransfer -Source $URL -Destination $DownloadPath -Asynchronous -Authentication Ntlm -Credential $Cred 9 | while (($Job.JobState -eq "Transferring") -or ($Job.JobState -eq "Connecting")) { 10 | If ($Job.JobState -eq "Connecting"){ 11 | Write-Host "BITS Job state is: $($Job.JobState)" 12 | } 13 | If ($Job.JobState -eq "Transferring"){ 14 | Write-Host "BITS Job state is: $($Job.JobState). $($Job.BytesTransferred) bytes transferred of $($Job.BytesTotal) total" 15 | } 16 | 17 | Start-Sleep -second 1 18 | } 19 | Switch($Job.JobState){ 20 | "Transferred" { 21 | Write-Host "BITS Job state is: $($Job.JobState). $($Job.BytesTransferred) bytes transferred of $($Job.BytesTotal) total" 22 | Complete-BitsTransfer -BitsJob $Job 23 | } 24 | "Error" {$Job | Format-List } # List the errors. 25 | default {Write-Host "Default action"} # Perform corrective action. 26 | } 27 | 28 | 29 | # Check P2P efficiency via the Event Log 30 | $Events = Get-WinEvent -FilterHashTable @{ LogName="*Bits*"; ID=60; Data="$URL" } | foreach { 31 | $_ | Add-Member -MemberType NoteProperty -Name name -Value $_.Properties[1].Value; 32 | $_ | Add-Member -MemberType NoteProperty -Name url -Value $_.Properties[3].Value; 33 | $_ | Add-Member -MemberType NoteProperty -Name bytesTotal -Value $_.Properties[8].Value; 34 | $_ | Add-Member -MemberType NoteProperty -Name bytesTransferred -Value $_.Properties[9].Value; 35 | $_ | Add-Member -MemberType NoteProperty -Name bytesTransferredFromPeer -Value $_.Properties[12].Value -PassThru; 36 | } 37 | $events | Sort-Object TimeCreated -Descending | Select -First 1 TimeCreated, url, bytesTotal, bytesTransferred, bytesTransferredFromPeer -------------------------------------------------------------------------------- /Large-Scale BCMon Testing/Invoke-BCMonProbeV2.ps1: -------------------------------------------------------------------------------- 1 | $HealthCheckPath = "\\CM01\HealthCheck$" 2 | $BCMonResultPath = "$HealthCheckPath\BCMonResults" 3 | $ProbeType = "/ProbeV2" 4 | $ProbeLogfile = "C:\Windows\temp\BCMon-ProbeTest.txt" 5 | 6 | #Check Architecture for BCMon 7 | if ([System.Environment]::Is64BitProcess){ 8 | $Architecture = "x64" 9 | } else { 10 | $Architecture = "x86" 11 | } 12 | $BCMonPath = "$HealthCheckPath\BCMon\$Architecture\bcmon.exe" 13 | 14 | If(test-path C:\Windows\temp\BCMon-ProbeTest.txt){ 15 | remove-item C:\Windows\temp\BCMon-ProbeTest.txt -force 16 | } 17 | 18 | $Process = Get-Process bcmon -ErrorAction SilentlyContinue 19 | If($Process){ 20 | stop-process -name bcmon -force 21 | } 22 | 23 | # Get IPv4 address only 24 | $IPv4 = Get-WmiObject win32_networkadapterconfiguration | Where-Object { $_.IPEnabled -eq $true } | Select -ExpandProperty ipaddress | Select -First 1 25 | 26 | # Copy the BCMon.exe utility 27 | Copy-Item -Path $BCMonPath -Destination "C:\Windows\Temp" 28 | 29 | # Delete any existing Firewall rule for BCMon 30 | netsh advfirewall firewall delete rule name="2Pint Software BCMon" 31 | 32 | # Create Firewall rule to allow BCMon to run 33 | netsh advfirewall firewall add rule name="2Pint Software BCMon" dir=in action=allow program="C:\Windows\Temp\BCMon.exe" enable=yes 34 | 35 | $Cmd = "C:\Windows\System32\cmd.exe" 36 | 37 | If(Test-path -Path $ProbeLogfile){Remove-Item $ProbeLogfile -Force } 38 | $Arglist = "/C C:\Windows\temp\BCMon.exe $ProbeType $IPv4 >> $ProbeLogfile" 39 | Start-Process -Filepath $Cmd -Argumentlist $Arglist -NoNewWindow 40 | 41 | # Allow 10 seconds to connect to the probe(s) 42 | start-sleep 10 43 | 44 | # Stop BCMon 45 | $Process = Get-Process bcmon -ErrorAction SilentlyContinue 46 | If($Process) 47 | { 48 | stop-process -name bcmon -force 49 | } 50 | 51 | # Make a note of connection type in the log 52 | if(Get-NetAdapter -Name "*" -Physical | Where-Object{$_.MediaType -eq "802.3" -and $_.Status -eq "Up"}){$CONNECTIONTYPE = "WIRED"} 53 | elseif(!(Get-NetAdapter -Name "*" -Physical | Where-Object{$_.MediaType -ne "802.3" -and $_.Status -eq "Up"})){$CONNECTIONTYPE = "WIRELESS"} 54 | else{$CONNECTIONTYPE = "NA"} 55 | Write-Output "Connection Type: $CONNECTIONTYPE" | Out-File $ProbeLogfile -Append ascii 56 | 57 | # Copy the log file 58 | xcopy $ProbeLogfile "$BCMonResultPath\$IPv4\" /y/i 59 | -------------------------------------------------------------------------------- /ConfigMgr Configuration Item (CI) to Enable and Tune BranchCache/Source/CacheSize_REMEDIATE.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Sets the Branchcache Cache size 4 | 5 | .DESCRIPTION 6 | Sets the Branchcache Cache according to free disk space % so that as disk space reduces the cache can be auto-adjusted 7 | Note that the default in this script are fairly conservative - so feel free to be more agressive with the cache size! 8 | 9 | 10 | .NOTES 11 | AUTHOR: 2Pint Software 12 | EMAIL: support@2pintsoftware.com 13 | TUNER VERSION: 1.0.1.3 14 | DATE:23 March 2021 15 | 16 | CHANGE LOG: 17 | 1.0.0.0 : 12/10/2017 : Initial version of script 18 | 1.0.0.2 : 10/06/2018 : Added a bit more logging 19 | 1.0.0.3 : 8/7/2020 : Added support for Windows Server and improved logging 20 | 1.0.0.4 : 5/3/2021 : Slight tweak to cache size calc 21 | 22 | .LINK 23 | https://2pintsoftware.com 24 | #> 25 | 26 | $Logfile = "C:\Windows\Temp\BCTuner_Cache_Remediation.log" 27 | 28 | # Delete any existing logfile if it exists 29 | If (Test-Path $Logfile){Remove-Item $Logfile -Force -ErrorAction SilentlyContinue -Confirm:$false} 30 | 31 | Function Write-Log{ 32 | param ( 33 | [Parameter(Mandatory = $true)] 34 | [string]$Message 35 | ) 36 | 37 | $TimeGenerated = $(Get-Date -UFormat "%D %T") 38 | $Line = "$TimeGenerated : $Message" 39 | Add-Content -Value $Line -Path $LogFile -Encoding Ascii 40 | 41 | } 42 | 43 | #======================================= 44 | # Get the free space on the system disk as % 45 | #======================================= 46 | Function Get-FreeSystemDiskspace 47 | { 48 | # Get the free space from WMI and return as % 49 | $SystemDrive = Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='$env:SystemDrive'" 50 | [int]$ReturnVal = $Systemdrive.FreeSpace*100/$Systemdrive.Size 51 | return $ReturnVal 52 | } 53 | #============== 54 | # End Function 55 | #============== 56 | 57 | #================================================================================ 58 | # Selects the best cache size based on free diskspace - edit these to your preferences 59 | #================================================================================ 60 | Function Check-BranchCachesize{ 61 | param([int]$CurrentFreeSpace) 62 | begin{ 63 | switch($CurrentFreeSpace){ 64 | {$_ -lt 10 -and $_ -ge 5}{$NewCachePercent = 5} #if less than 10% but more than 5% new cache should be 5% 65 | {$_ -lt 50 -and $_ -ge 10}{$NewCachePercent = 10} #if less than 50% but more than 10% new cache should be 10% 66 | {$_ -lt 75 -and $_ -ge 50}{$NewCachePercent = 20}##if less than 75% but more than 50% new cache should be 20% 67 | {$_ -ge 75}{$NewCachePercent = 50}##if more than 75% new cache should be 50% 68 | default{$NewCachePercent = 5}#default value 69 | } 70 | Return $NewCachePercent 71 | } 72 | } 73 | #============== 74 | # End Function 75 | #============== 76 | 77 | Write-Log "BC Cache Size Remediation is Running" 78 | 79 | # Get the size available and then return the cache space needed 80 | $FreeSpaceAvailable = Get-FreeSystemDiskspace 81 | $CacheSize = Check-BranchCachesize -CurrentFreeSpace $FreeSpaceAvailable 82 | 83 | Write-Log "Free Space Check Returned: $FreeSpaceAvailable %" 84 | Write-Log "Cache size should be: $CacheSize %" 85 | Write-Log "Setting the new cache size" 86 | 87 | # Call netsh to set the new cache size 88 | $CacheSizeCmd = {netsh branchcache set cachesize size=$CacheSize percent=TRUE} 89 | Invoke-Command -ScriptBlock $CacheSizeCmd | Out-File $Logfile -Append 90 | Write-Log "BC Cache Size Remediation is Completed" 91 | -------------------------------------------------------------------------------- /Creating P2P Test Packages/New-SetOfP2PTestPackages.ps1: -------------------------------------------------------------------------------- 1 | # 2 | # Written by Johan Arwidmark, @jarwidmark on Twitter 3 | # 4 | 5 | # Global Settings 6 | $SiteCode = "PS1" 7 | $PackagePrefix = "P2P Test Package" 8 | $CMPackageFolder = "$($SiteCode):\Package\P2P Testing" 9 | $Collection = "All Workstations" 10 | $DPGroup = "HQ DPs" 11 | $RootSourceFolderForPackages = "\\CM01\Sources\P2P Test Packages" # Will create one application for each first level subfolder 12 | 13 | # Import the ConfigurationManager.psd1 module 14 | if((Get-Module ConfigurationManager) -eq $null) { 15 | Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1" 16 | } 17 | 18 | # Connect to the site's drive if it is not already present 19 | if((Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue) -eq $null) { 20 | New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $ProviderMachineName 21 | } 22 | 23 | Set-Location "C:" 24 | 25 | # Check for source folder 26 | If (!(Test-Path -Path $RootSourceFolderForPackages)){ 27 | Write-Warning "Specified source folder: $RootSourceFolderForPackages, does not exist, aborting..." 28 | Break 29 | } 30 | 31 | # Get first level folders (only) 32 | $DataSources = Get-ChildItem $RootSourceFolderForPackages -directory 33 | If (!($DataSources | Measure-Object).count -gt 0) { 34 | Write-Warning "No subfolders found in source folder: $RootSourceFolderForPackages, aborting..." 35 | Break 36 | } 37 | 38 | 39 | Set-Location "$($SiteCode):\" 40 | 41 | # Check for DP Group 42 | If (!(Get-CMDistributionPointGroup -Name $DPGroup)){ 43 | Write-Warning "Specified DP Group: $DPGroup, does not exist, aborting..." 44 | Break 45 | } 46 | 47 | # Create package folder in console if missing 48 | If (!(Test-path $CMPackageFolder)){ 49 | New-Item $CMPackageFolder 50 | } 51 | 52 | # Create All Workstations collection if missing 53 | If (!(Get-CMCollection -Name $Collection)){ 54 | 55 | New-CMCollection -CollectionType Device -Name $Collection -LimitingCollectionName "All Systems" 56 | $Query = @" 57 | Select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,SMS_R_SYSTEM.ResourceDomainORWorkgroup,SMS_R_SYSTEM.Client from SMS_R_System where SMS_R_System.OperatingSystemNameandVersion like "%Workstation%" 58 | "@ 59 | Add-CMDeviceCollectionQueryMembershipRule -CollectionName $Collection -RuleName $Collection -QueryExpression $Query 60 | } 61 | 62 | 63 | # Create P2P Test packages based on folder names 64 | foreach ($Folder in $DataSources){ 65 | 66 | $PackageName = "$PackagePrefix - $($Folder.Name)" 67 | $DataSource = $Folder.FullName 68 | $CommandLine = "cmd /c echo . > C:\Windows\Temp\$($Folder.Name).txt" 69 | 70 | Write-Host "Working on: $($Folder.Name)" 71 | # Create package 72 | $Package = New-CMPackage -Name $PackageName -Path $DataSource 73 | $Package | Set-CMPackage -DistributionPriority Normal 74 | 75 | # Move package to correct ConfigMgr folder 76 | Move-CMObject -FolderPath $CMPackageFolder -InputObject $Package 77 | 78 | # Create program 79 | $Program = New-CMProgram -PackageName "$PackageName" -StandardProgramName "$PackageName" -CommandLine $CommandLine -ProgramRunType WhetherOrNotUserIsLoggedOn -RunMode RunWithAdministrativeRights 80 | 81 | # Distribute the package 82 | Start-CMContentDistribution -PackageId $Package.PackageID -DistributionPointGroupName $DPGroup 83 | 84 | # Deploy the program as available 85 | New-CMPackageDeployment -StandardProgram -PackageId $Package.PackageID -ProgramName "$($Program.PackageName)" -CollectionName $Collection -DeployPurpose Available -FastNetworkOption DownloadContentFromDistributionPointAndRunLocally -SlowNetworkOption DoNotRunProgram 86 | } 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /ConfigMgr Configuration Item (CI) to Enable and Tune BranchCache/Source/CacheSize_DISCOVERY.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Checks size of branchcache 4 | 5 | .DESCRIPTION 6 | Checks that the current size of the branch cache is still correct 7 | Note that the default in this script are fairly conservative - so feel free to be more agressive with the cache size! 8 | 9 | .NOTES 10 | AUTHOR: 2Pint Software 11 | EMAIL: support@2pintsoftware.com 12 | TUNER VERSION: 1.0.1.3 13 | DATE:23 March 2021 14 | 15 | CHANGE LOG: 16 | 1.0.0.0 : 12/10/2017 : Initial version of script 17 | 1.0.0.2 : 10/06/2018 : Added a bit more logging 18 | 1.0.0.3 : 8/7/2020 : Added support for Windows Server and improved logging 19 | 1.0.0.4 : 5/3/2021 : Changes to support non-English languages 20 | 21 | 22 | .LINK 23 | https://2pintsoftware.com 24 | #> 25 | 26 | 27 | $Logfile = "C:\Windows\Temp\BCTuner_Cache_Discovery.log" 28 | 29 | # Delete any existing logfile if it exists 30 | If (Test-Path $Logfile){Remove-Item $Logfile -Force -ErrorAction SilentlyContinue -Confirm:$false} 31 | 32 | Function Write-Log{ 33 | param ( 34 | [Parameter(Mandatory = $true)] 35 | [string]$Message 36 | ) 37 | 38 | $TimeGenerated = $(Get-Date -UFormat "%D %T") 39 | $Line = "$TimeGenerated : $Message" 40 | Add-Content -Value $Line -Path $LogFile -Encoding Ascii 41 | 42 | } 43 | 44 | #======================================= 45 | # Get the free space on the system disk as % 46 | #======================================= 47 | Function Get-FreeSystemDiskspace{ 48 | # Get the free space from WMI and return as % 49 | $SystemDrive = Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='$env:SystemDrive'" 50 | [int]$ReturnVal = $Systemdrive.FreeSpace*100/$Systemdrive.Size 51 | return $ReturnVal 52 | } 53 | #============== 54 | # End Function 55 | #============== 56 | 57 | #============================================================================= 58 | # Selects the best cache size based on free diskspace - EDIT THE DEFAULTS HERE 59 | #============================================================================= 60 | Function Check-BranchCachesize{ 61 | param([int]$CurrentFreeSpace) 62 | begin{ 63 | switch($CurrentFreeSpace){ 64 | {$_ -lt 10 -and $_ -ge 5}{$NewCachePercent = 5} #if less than 10% but more than 5% new cache should be 5% 65 | {$_ -lt 50 -and $_ -ge 10}{$NewCachePercent = 10} #if less than 50% but more than 10% new cache should be 10% 66 | {$_ -lt 75 -and $_ -ge 50}{$NewCachePercent = 20}##if less than 75% but more than 50% new cache should be 20% 67 | {$_ -ge 75}{$NewCachePercent = 50}##if more than 75% new cache should be 50% 68 | default{$NewCachePercent = 5}#default value 69 | } 70 | Return $NewCachePercent 71 | } 72 | } 73 | #============== 74 | # End Function 75 | #============== 76 | Write-Log "BC Cache Size Check is Running" 77 | 78 | # First we assume the client is compliant 79 | $Compliance = "Compliant" 80 | 81 | #============================================================== 82 | # Get the size available and then return the cache space needed 83 | #============================================================== 84 | $FreeSpaceAvailable = Get-FreeSystemDiskspace 85 | $CacheSize = Check-BranchCachesize -CurrentFreeSpace $FreeSpaceAvailable 86 | 87 | Write-Log "Free Space Check Returned: $FreeSpaceAvailable %" 88 | Write-Log "BranchCache Cache size should be: $CacheSize %" 89 | 90 | #============================================================== 91 | # Call netsh to carry out a match against the status 92 | #============================================================== 93 | Write-Log "Checking current Cache size by running netsh cmd" 94 | Write-Log "netsh output:" 95 | $ShowStatusLocalCmd = {netsh branchcache show localcache} 96 | $ShowStatus = Invoke-Command -ScriptBlock $ShowStatusLocalCmd 97 | #need to convert the array to string for the logging 98 | $ShowStatusString = $ShowStatus | Out-String 99 | Write-Log $ShowStatusString 100 | 101 | # Checking cache size has been set 102 | if(@($ShowStatus | Select-String -SimpleMatch -Pattern "%")[0].ToString() -match "$CacheSize%") 103 | { 104 | $Compliance = "Compliant" 105 | } 106 | else 107 | { 108 | $Compliance = "Non-Compliant" 109 | } 110 | 111 | Write-Log "BC Cache Size Check Returned: $Compliance" 112 | Return $Compliance 113 | -------------------------------------------------------------------------------- /Large-Scale BCMon Testing/Invoke-MassBCMonProbing.ps1: -------------------------------------------------------------------------------- 1 | $SiteServer= "CM01" 2 | $DatabaseServer= "CM01" 3 | $Database = "CM_PS1" 4 | $Query= $("with T1 as(SELECT (PARSENAME(b.IPAddress,4)+'.'+PARSENAME(b.IPAddress,3)+'.'+PARSENAME(b.IPAddress,2)) as subnet, s.Netbios_Name0, b.IPAddress FROM vSMS_R_System as s INNER JOIN BGB_ResStatus as b on b.ResourceID = s.ItemKey WHERE b.OnlineStatus = '1'), limit as(select COUNT(SUBNET) as TOTAL, SUBNET from T1 group by subnet ),t2 as(select T1.Subnet, t1.IPAddress, t1.Netbios_Name0, ROW_NUMBER() OVER (Partition by T1.Subnet ORDER BY T1.IPaddress DESC) as rn from T1 ) Select T2.Subnet , T2.IPAddress , t2.Netbios_Name0 from t2 left outer join limit on limit.subnet = t2.subnet where rn <= 4 and limit.TOTAL >= 4 ") 5 | 6 | # Run SQL Query 7 | $Datatable = New-Object System.Data.DataTable 8 | $Connection = New-Object System.Data.SQLClient.SQLConnection 9 | $Connection.ConnectionString = "server='$DatabaseServer';database='$Database';trusted_connection=true;" 10 | $Connection.Open() 11 | $Command = New-Object System.Data.SQLClient.SQLCommand 12 | $Command.Connection = $Connection 13 | $Command.CommandText = $Query 14 | $Reader = $Command.ExecuteReader() 15 | $Datatable.Load($Reader) 16 | $Connection.Close() 17 | 18 | #Region helperfunctions 19 | function Get-CMModule 20 | #This function gets the configMgr module 21 | { 22 | [CmdletBinding()] 23 | param() 24 | Try 25 | { 26 | Write-Verbose "Attempting to import SCCM Module" 27 | #Retrieves the fcnction from ConfigMgr installation path. 28 | Import-Module (Join-Path $(Split-Path $ENV:SMS_ADMIN_UI_PATH) ConfigurationManager.psd1) -Verbose:$false 29 | Write-Verbose "Succesfully imported the SCCM Module" 30 | } 31 | Catch 32 | { 33 | Throw "Failure to import SCCM Cmdlets." 34 | } 35 | } 36 | 37 | function Test-ConfigMgrAvailable 38 | #Tests if ConfigMgr is availble so that the SMSProvider and configmgr cmdlets can help. 39 | { 40 | [CMdletbinding()] 41 | Param 42 | ( 43 | [Parameter(Mandatory = $false)] 44 | [bool]$Remediate 45 | ) 46 | try 47 | { 48 | if((Test-Module -ModuleName ConfigurationManager -Remediate:$true) -eq $false) 49 | #Checks to see if the Configuration Manager module is loaded or not and then since the remediate flag is set automatically imports it. 50 | { 51 | throw "You have not loaded the configuration manager module please load the appropriate module and try again." 52 | #Throws this error if even after the remediation or if the remediation fails. 53 | } 54 | write-Verbose "ConfigurationManager Module is loaded" 55 | Write-Verbose "Checking if current drive is a CMDrive" 56 | if((Get-location -Verbose:$false).Path -ne (Get-location -PSProvider 'CmSite' -Verbose:$false).Path) 57 | #Checks if the current location is the - PS provider for the CMSite server. 58 | { 59 | Write-Verbose -Message "The location is NOT currently the CMDrive" 60 | if($Remediate) 61 | #If the remediation field is set then it attempts to set the current location of the path to the CMSite server path. 62 | { 63 | Write-Verbose -Message "Remediation was requested now attempting to set location to the the CM PSDrive" 64 | Set-Location -Path (((Get-PSDrive -PSProvider CMSite -Verbose:$false).Name) + ":") -Verbose:$false 65 | Write-Verbose -Message "Succesfully connected to the CMDrive" 66 | #Sets the location properly to the PSDrive. 67 | } 68 | 69 | else 70 | { 71 | throw "You are not currently connected to a CMSite Provider Please Connect and try again" 72 | } 73 | } 74 | write-Verbose "Succesfully validated connection to a CMProvider" 75 | return $true 76 | } 77 | catch 78 | { 79 | $errorMessage = $_.Exception.Message 80 | write-error -Exception CMPatching -Message $errorMessage 81 | return $false 82 | } 83 | } 84 | 85 | function Test-Module 86 | #Function that is designed to test a module if it is loaded or not. 87 | { 88 | [CMdletbinding()] 89 | Param 90 | ( 91 | [Parameter(Mandatory = $true)] 92 | [String]$ModuleName, 93 | [Parameter(Mandatory = $false)] 94 | [bool]$Remediate 95 | ) 96 | If(Get-Module -Name $ModuleName) 97 | #Checks if the module is currently loaded and if it is then return true. 98 | { 99 | Write-Verbose -Message "The module was already loaded return TRUE" 100 | return $true 101 | } 102 | If((Get-Module -Name $ModuleName) -ne $true) 103 | #Checks if the module is NOT loaded and if it's not loaded then check to see if remediation is requested. 104 | { 105 | Write-Verbose -Message "The Module was not already loaded evaluate if remediation flag was set" 106 | if($Remediate -eq $true) 107 | #If the remediation flag is selected then attempt to import the module. 108 | { 109 | try 110 | { 111 | if($ModuleName -eq "ConfigurationManager") 112 | #If the module requested is the Configuration Manager module use the below method to try to import the ConfigMGr Module. 113 | { 114 | Write-Verbose -Message "Non-Standard module requested run pre-written function" 115 | Get-CMModule 116 | #Runs the command to get the COnfigMgr module if its needed. 117 | Write-Verbose -Message "Succesfully loaded the module" 118 | return $true 119 | } 120 | else 121 | { 122 | Write-Verbose -Message "Remediation flag WAS set now attempting to import module $($ModuleName)" 123 | Import-Module -Name $ModuleName 124 | #Import the other module as needed - if they have no custom requirements. 125 | Write-Verbose -Message "Succesfully improted the module $ModuleName" 126 | Return $true 127 | } 128 | } 129 | catch 130 | { 131 | Write-Error -Message "Failed to import the module $($ModuleName)" 132 | Set-Location $StartingLocation 133 | break 134 | } 135 | } 136 | else { 137 | #Else return the fact that it's not applicable and return false from the execution. 138 | { 139 | Return $false 140 | } 141 | } 142 | } 143 | } 144 | #endregion HelperFunctions 145 | 146 | # Connect to the Site Server 147 | if(!(Test-ConfigMgrAvailable -Remediate:$true)){ 148 | Write-Log -Message "We were unable to load the ConfigMgr Cmdlets and unable to connect to the CM provider will now exit." -LogLevel 3 149 | break 150 | } 151 | 152 | 153 | # Run ProbeMatch Script on all ProbeMatch Clients (odd row numbers) 154 | $ScriptName = "Run BCMon ProbeMatch" 155 | $ScriptGuid = (Get-CMScript -ScriptName $ScriptName -Fast).ScriptGuid 156 | 157 | $count = 0 158 | Foreach ($Row in $Datatable){ 159 | 160 | $count++ 161 | 162 | if($count%2 -eq 0 ){ 163 | # Do Nothing 164 | } 165 | else { 166 | write-host "$($ScriptName) on $($Row.Netbios_Name0) in subnet $($Row.subnet).0 " 167 | $CMDevice = Get-CMDevice -Name $Row.Netbios_Name0 -Fast 168 | 169 | Invoke-CMScript -Device $CMDevice -ScriptGuid $ScriptGuid 170 | } 171 | 172 | } 173 | 174 | Write-Host "" 175 | Write-Host "Waiting 60 seconds before starting ProbeV2..." 176 | Write-Host "" 177 | Start-Sleep -Seconds 60 178 | 179 | 180 | # Run ProbeV2 Script on all ProbeV2 Clients (even row numbers) 181 | $ScriptName = "Run BCMon ProbeV2" 182 | $ScriptGuid = (Get-CMScript -ScriptName $ScriptName -Fast).ScriptGuid 183 | 184 | $count = 0 185 | Foreach ($Row in $Datatable){ 186 | 187 | $count++ 188 | 189 | if($count%2 -eq 0 ){ 190 | write-host "$($ScriptName) on $($Row.Netbios_Name0) in subnet $($Row.subnet).0 " 191 | $CMDevice = Get-CMDevice -Name $Row.Netbios_Name0 -Fast 192 | 193 | Invoke-CMScript -Device $CMDevice -ScriptGuid $ScriptGuid 194 | 195 | } 196 | else { 197 | # Do Nothing 198 | } 199 | 200 | } 201 | 202 | 203 | 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /ConfigMgr Configuration Item (CI) to Enable and Tune BranchCache/Source/MAIN_REMEDIATE.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .DISCLAIMER Use at your own risk. Test it. Change it to suit your setup. 3 | If it breaks your sh*t we are most definitely NOT to blame. We have alibis. 4 | 5 | .SYNOPSIS 6 | Remediate Branchcache port and configures that branchcache service 7 | 8 | .DESCRIPTION 9 | 1. Set the required Port Number for P2P transfers 10 | 2. Configure the BranchCache Service for Distributed MOde 11 | 3. Delete the old reservation on port 80 if it's still there 12 | 4. Sets the Cache data TTL value 13 | 5. Configures the BC service to Autostart and starts it 14 | 6. Configures the Windows Firewall 15 | 7. Creates a Logfile in the C:\Windows\Temp folder 16 | 17 | 18 | 19 | AUTHOR: 2Pint Software 20 | EMAIL: support@2pintsoftware.com 21 | TUNER VERSION: 1.0.1.3 22 | DATE: 30 March 2021 23 | 24 | CHANGE LOG: 25 | 1.0.0.0 : 12/10/2017 : Initial version of script 26 | 1.0.0.2 : 12/10/2017 : added check for the old port 80 reservation - deletes it if exist 27 | 1.0.0.3 : 12/10/2017 : Sets firewall rules to the correct port - SCCM can create multiple rules which aren't all set by default 28 | 1.0.0.4 : 12/10/2017 : Consolidated the Port Check and service check into one to reduce errors 29 | 1.0.0.5 : 12/10/2017 : Added Battery Check 'Serve Peers on Battery' - set to TRUE/FALSE 30 | 1.0.0.6 : 09/06/2018 : Changed the order of things and added a service stop. Also added Cache TTL 31 | 1.0.0.7 : 14/08/2018 : Removes all BC Firewall Rules first - and then re-adds them later. Also removes Hosted Cache Rules. 32 | 1.0.0.8 : 16/08/2018 : Added a check to see if the Windows Firewall is in play - if not - no point fiddling! 33 | 1.0.0.9 : 23/07/2019 : Improved url reservation handling - changed the order a little 34 | 1.0.1.0 : 12/08/2019 : Improved url reservation handling to remove old url if the port is changed 35 | 1.0.1.1 : 8/7/2020 : Added support for Windows Server and improved logging 36 | 1.0.1.2 : 5/3/2021 : Added support for non-English languages and added a 'clear event log' step 37 | 1.0.1.3 : 23/3/2021 : Remove Firewall detection as it has proved unreliable 38 | 39 | .LINK 40 | https://2pintsoftware.com 41 | #> 42 | 43 | $Logfile = "C:\Windows\Temp\BCTuner_Main_Remediation.log" 44 | 45 | # Delete any existing logfile if it exists 46 | If (Test-Path $Logfile){Remove-Item $Logfile -Force -ErrorAction SilentlyContinue -Confirm:$false} 47 | 48 | Function Write-Log{ 49 | param ( 50 | [Parameter(Mandatory = $true)] 51 | [string]$Message 52 | ) 53 | 54 | $TimeGenerated = $(Get-Date -UFormat "%D %T") 55 | $Line = "$TimeGenerated : $Message" 56 | Add-Content -Value $Line -Path $LogFile -Encoding Ascii 57 | 58 | } 59 | 60 | # EDIT THIS VARIABLE TO THE PORT THAT YOU WANT BRANCHCACHE TO USE 61 | # THIS SHOULD BE THE SAME AS THE EQUIVALENT IN THE DISCOVERY SCRIPT 62 | #--------------<<<<< 63 | $BCPort = 1337 64 | #--------------<<<<< 65 | # SET THIS VARIABLE TO DETERMINE IF CLIENTS CAN SERVE PEERS WHILE ON BATTERY 66 | # TRUE/FALSE 67 | #-----------------------<<<<< 68 | $ServeOnBattery = "TRUE" 69 | #-----------------------<<<<< 70 | 71 | # SET THIS VARIABLE to set the TTL - this is the time (Days) that BranchCache will keep content in the cache 72 | #-----------------------<<<<< 73 | $TTL = 180 74 | #-----------------------<<<<< 75 | 76 | $RegPath = 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\PeerDist\DownloadManager\Peers' 77 | $TTLRegPath = 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\PeerDist' 78 | $SetBCCommand = {netsh branchcache set service mode=distributed serveonbattery=$ServeOnBattery} 79 | $ShowHttpUrl = netsh http show url 80 | $DeleteResCmd = {netsh http delete urlacl url=$urlToDelete} 81 | $DisableBCCommand = {netsh branchcache set service mode=disabled} 82 | 83 | Write-Log "BC Port, Firewall and Service Remediation is Running" 84 | 85 | #================================================================ 86 | # If Windows Server, add BranchCache feature if not installed 87 | #================================================================ 88 | 89 | $OSCaption = (Get-WmiObject win32_operatingsystem).caption 90 | If ($OSCaption -like "*Windows Server*"){ 91 | Write-Log "OS is a server, check for BranchCache feature" 92 | $Result=Get-WindowsFeature BranchCache 93 | 94 | If($Result.Installed -eq $true){ 95 | Write-Log "BranchCache feature found, all ok, continuing" 96 | } 97 | Else{ 98 | Write-Log "BranchCache feature not found, adding it" 99 | Install-WindowsFeature BranchCache 100 | } 101 | } 102 | 103 | 104 | #--------------------------------------------------------------------------------------- 105 | # Stop BranchCache (Only if installed) 106 | #--------------------------------------------------------------------------------------- 107 | $s = Get-Service -name PeerDistSvc -ErrorAction SilentlyContinue 108 | 109 | If ($s){ 110 | Stop-Service $s.name -Force 111 | } 112 | 113 | #--------------------------------------------------------------------------------------- 114 | # Set the correct BranchCache ListenPort in the registry 115 | #--------------------------------------------------------------------------------------- 116 | # If the key doesn't exist - create it, and set the port, job done 117 | 118 | if (!(Get-Item -path $RegPath\Connection -ErrorAction SilentlyContinue)){ 119 | Write-Log "Custom BC Port Reg Key didn't exist - remediating" 120 | New-Item -Path $RegPath -name Connection -force 121 | New-ItemProperty -Path $RegPath\Connection -Name ListenPort -PropertyType DWORD -Value $BCPort 122 | New-ItemProperty -Path $RegPath\Connection -Name ConnectPort -PropertyType DWORD -Value $BCPort 123 | } 124 | 125 | # If the key already exists, check the ListenPort value and change if required 126 | if((Get-ItemProperty -path $RegPath\Connection -Name ListenPort -ErrorAction SilentlyContinue).ListenPort -ne $BCPort){ 127 | Write-Log "Custom BC ListenPort Reg value exists but is incorrect - remediating" 128 | Set-ItemProperty -Path $RegPath\Connection -Name ListenPort -Value $BCPort 129 | } 130 | 131 | # If the key already exists, check the ConnectPort value and change if required 132 | if((Get-ItemProperty -path $RegPath\Connection -Name ConnectPort -ErrorAction SilentlyContinue).ConnectPort -ne $BCPort){ 133 | Write-Log "Custom BC ConnectPort Reg value exists but is incorrect - remediating" 134 | Set-ItemProperty -Path $RegPath\Connection -Name ConnectPort -Value $BCPort 135 | } 136 | 137 | #--------------------------------------------------------------------------------------- 138 | # Set the correct TTL - this is the time (Days) that BranchCache will keep content in the cache 139 | #--------------------------------------------------------------------------------------- 140 | # If the key doesn't exist - create it, and set the TTL, job done 141 | 142 | if (!(Get-Item -path $TTLRegPath\Retrieval -ErrorAction SilentlyContinue)){ 143 | New-Item -Path $TTLRegPath -name Retrieval -force 144 | New-ItemProperty -Path $TTLRegPath\Retrieval -Name SegmentTTL -PropertyType DWORD -Value $TTL 145 | } 146 | # If the key already exists, check the value and change if required 147 | if(((Get-ItemProperty -path $TTLRegPath\Retrieval -Name SegmentTTL -ErrorAction SilentlyContinue).SegmentTTL) -ne $TTL){ 148 | Set-ItemProperty -Path $TTLRegPath\Retrieval -Name SegmentTTL -Value $TTL 149 | } 150 | 151 | Write-Log "BranchCache TTL Remediation Complete" 152 | 153 | 154 | #--------------------------------------------------------------------------------------- 155 | # Remove existing F/W Rules (if it's enabled) in case they are a mess! 156 | #--------------------------------------------------------------------------------------- 157 | 158 | Write-Log "Removing old F/W Rules" 159 | 160 | #Remove Content Retrieval Rules (IN/OUT) 161 | netsh advfirewall firewall delete rule name="BranchCache Content Retrieval (HTTP-Out)" 162 | netsh advfirewall firewall delete rule name="BranchCache Content Retrieval (HTTP-In)" 163 | 164 | #Remove Content Discovery Rules (IN/OUT) 165 | netsh advfirewall firewall delete rule name="BranchCache Peer Discovery (WSD-Out)" 166 | netsh advfirewall firewall delete rule name="BranchCache Peer Discovery (WSD-In)" 167 | 168 | 169 | #--------------------------------------------------------------------------------------- 170 | # END Remove existing F/W Rules in case they are a mess! 171 | #--------------------------------------------------------------------------------------- 172 | 173 | #--------------------------------------------------------------------------------------- 174 | # Enable BranchCache distributed mode (this also sets the correct 'Serve Peers on Battery' Mode) 175 | # It will also re-create the F/W Rules 176 | #--------------------------------------------------------------------------------------- 177 | Write-Log "Setting BranchCache service to Distributed Mode" 178 | Invoke-Command -ScriptBlock $SetBCCommand 179 | 180 | #--------------------------------------------------------------------------------------- 181 | # Clear the BC Event Log so that subsequent discoveries don't pickup old events 182 | #--------------------------------------------------------------------------------------- 183 | Write-Log "Clearing the Event Log" 184 | Function Clear-WinEvent { 185 | [CmdletBinding(SupportsShouldProcess=$True)] 186 | Param 187 | ([String]$LogName) 188 | Process { 189 | If ($PSCmdlet.ShouldProcess("$LogName", "Clear log file")) 190 | { 191 | [System.Diagnostics.Eventing.Reader.EventLogSession]::` 192 | GlobalSession.ClearLog("$LogName") 193 | } # End of If 194 | } # End of Process 195 | } # End of creating the function 196 | 197 | # Calling the function Clear-WinEvent 198 | Clear-Host 199 | $BCLog = "Microsoft-Windows-BranchCache/Operational" 200 | Get-WinEvent -ListLog $BCLog 201 | Clear-WinEvent -LogName $BCLog 202 | 203 | 204 | #--------------------------------------------------------------------------------------- 205 | # Set the service to auto-start and start it if not running 206 | #--------------------------------------------------------------------------------------- 207 | Set-Service -Name "peerdistsvc" -StartupType automatic 208 | if ((Get-Service -Name PeerDistSvc).Status -ne "Running"){ 209 | Start-Service -Name PeerdistSvc 210 | } 211 | 212 | #--------------------------------------------------------------------------------------- 213 | # Remove the old existing URL reservation i.e remove any BranchCache url reservation that DOES NOT have the current Port 214 | #--------------------------------------------------------------------------------------- 215 | 216 | # Checking for old obsolete port reservations - first, select all BranchCache url reservations 217 | $ResList = ($ShowHttpUrl | Select-String -SimpleMatch -Pattern "/116B50EB-ECE2-41ac-8429-9F9E963361B7/") 218 | 219 | ForEach($Res in $ResList){ 220 | 221 | $a = [regex]::Matches($Res, 'http(.*)') 222 | If($a -like "http://+:$BCPort*"){ 223 | Write-Log "Not deleting the current URL: $a" 224 | } 225 | else{ 226 | $urlToDelete=$a.Value.Trim() 227 | Invoke-Command -scriptblock $DeleteResCmd | Out-File $Logfile -Append 228 | } 229 | 230 | } 231 | -------------------------------------------------------------------------------- /ConfigMgr Configuration Item (CI) to Enable and Tune BranchCache/Source/MAIN_DISCOVERY.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Checks Branchcache is Using Desired Port and that the service is running 4 | 5 | .DESCRIPTION 6 | 1. Checks for event 7,8 in the BC event log - means that F/W is blocking BC 7 | 2. Checks that the Branchcache has been configured to use a specificTCP port 8 | 3. Checks the Branchcache Cache TTL value 9 | 4. Checks that the BranchCache service is set to 'Distributed Caching' mode 10 | 5. Checks the 'serve peers on battery power' capability 11 | 6. Finally - checks that the service is running and is set to AutoStart 12 | 7. If ANY of these checks fail - a status of Non-Compliance is reported 13 | 8. Creates a Logfile in the C:\Windows\Temp folder 14 | 15 | 16 | NOTES 17 | AUTHOR: 2Pint Software 18 | EMAIL: support@2pintsoftware.com 19 | TUNER VERSION: 1.0.1.3 20 | DATE: 23 March 2021 21 | 22 | CHANGE LOG: 23 | 1.0.0.0 : 12/10/2017 : Initial version of script 24 | 1.0.0.2 : 12/10/2017 : added logging and other minor tweaks 25 | 1.0.0.3 : 08/06/2018 : added some more logic for checking svc auto-start and state.. 26 | 1.0.0.4 : 09/06/2018 : consolidated the 'Serve Peers on Battery' and 'Cache TTL' check into this script 27 | 1.0.0.5 : 26/06/2018 : Added BranchCache Firewall Error Event check 28 | 1.0.0.6 : 23/07/2019 : Improved Port compliance checking 29 | 1.0.0.7 : 12/08/2019 : Improved Port checking - added ConnectPort (we were only checking ListenPort before) 30 | 1.0.0.8 : 8/7/2020 : Added support for Windows Server and improved logging 31 | 1.0.0.9 : 5/3/2021 : Added support for non-English languages 32 | 1.0.1.0 : 23/3/2021 : Added another check for firewall issues using netsh 33 | 34 | .LINK 35 | https://2pintsoftware.com 36 | #> 37 | 38 | $Logfile = "C:\Windows\Temp\BCTuner_Main_Discovery.log" 39 | 40 | # Delete any existing logfile if it exists 41 | If (Test-Path $Logfile){Remove-Item $Logfile -Force -ErrorAction SilentlyContinue -Confirm:$false} 42 | 43 | Function Write-Log{ 44 | param ( 45 | [Parameter(Mandatory = $true)] 46 | [string]$Message 47 | ) 48 | 49 | $TimeGenerated = $(Get-Date -UFormat "%D %T") 50 | $Line = "$TimeGenerated : $Message" 51 | Add-Content -Value $Line -Path $LogFile -Encoding Ascii 52 | 53 | } 54 | 55 | Write-Log "BC Port, Firewall and Service Check is Running" 56 | 57 | # Set this variable to the port number that you wanna check/change - if you want to leave it at the default BC port you MUST set this to 80 58 | # THIS SHOULD BE THE SAME AS THE EQUIVALENT VARIABLE IN THE REMEDIATION SCRIPT 59 | #-------------- 60 | $BCPort = 1337 61 | #-------------- 62 | # SET THIS VARIABLE TO DETERMINE IF CLIENTS CAN SERVE PEERS WHILE ON BATTERY POWER 63 | #-------------- 64 | $ServeOnBattery = "TRUE" 65 | #-------------- 66 | 67 | # Set this variable to check the cache TTL - this is the time (Days) that BranchCache will keep content in the cache 68 | #----------------------- 69 | $TTL = 180 70 | #----------------------- 71 | 72 | 73 | # First we assume the client is compliant 74 | $Compliance = "Compliant" 75 | 76 | #================================================================ 77 | # If Windows Server, check if BranchCache feature has been added 78 | #================================================================ 79 | 80 | $OSCaption = (Get-WmiObject win32_operatingsystem).caption 81 | If ($OSCaption -like "*Windows Server*"){ 82 | Write-Log "OS is a server, check for BranchCache feature" 83 | $Result=Get-WindowsFeature BranchCache 84 | 85 | If($Result.Installed -eq $true){ 86 | $Compliance = "Compliant" 87 | } 88 | Else{ 89 | $Compliance = "Feature Check Non-Compliant" 90 | Write-Log "Feature Check check - failed" 91 | Write-Log "Feature Check Returned - $Compliance" 92 | Return $Compliance 93 | } 94 | } 95 | 96 | 97 | 98 | #================================================================ 99 | # Check the event log for events 7,8 - meaning that BC+P2P is blocked 100 | #================================================================ 101 | 102 | $EventLogName = "Microsoft-Windows-BranchCache/Operational" 103 | 104 | # Check if the BC evt log exists and returns a result (or evt 7 or 8) - if not - then we are Compliant 105 | $log = try{ 106 | Get-WinEvent -LogName $EventLogName -ErrorAction Stop | Where-Object {$_.ID -eq 7 -or $_.ID -eq 8} 107 | } 108 | 109 | catch { 110 | Write-log "No BC event log found or the log is empty" 111 | } 112 | 113 | 114 | # If no results then we are compliant (either no log found or no results returned) 115 | if (!$log){ 116 | $Compliance = "Compliant" 117 | } 118 | # If the above query returns a result - set the status to Non-Compliant 119 | Else{ 120 | $Compliance = "Firewall Event Check Non-Compliant" 121 | Write-Log "BC Firewall Events check - failed" 122 | Write-Log "BC Firewall Events Check Returned - $Compliance" 123 | Return $Compliance 124 | } 125 | 126 | Write-Log "BC Firewall Event Check Returned - $Compliance" 127 | 128 | #========================================================= 129 | # Next Check that the netsh output for the firewall is ok 130 | #========================================================= 131 | 132 | # Call netsh to carry out a match against the status 133 | $ShowStatusAllCommand = {netsh branchcache show status all} 134 | $ShowStatusAll = Invoke-Command -ScriptBlock $ShowStatusAllCommand 135 | $ShowStatusAllMsg = $ShowStatusAll | Out-String 136 | Write-Log "netsh (show status all) output:" 137 | Write-Log $ShowStatusAllMsg 138 | 139 | $fw = try{ 140 | ($ShowStatusAll | Select-String -SimpleMatch -Pattern "Error Executing Action Display Firewall Rule Group Status:")[0].ToString() -match "Could not query Windows Firewall configuration" 141 | } 142 | catch [Exception]{ 143 | if (($_.Exception -match "You cannot call a method on a null-valued expression") -or ($_.Exception -match "Cannot index into a null array")){ 144 | Write-Log "No Firewall error reported" 145 | } 146 | } 147 | 148 | # If no results then we are compliant (no firewall error ) 149 | if (!$fw){ 150 | $Compliance = "Compliant" 151 | } 152 | # If the above query returns a result - set the status to Non-Compliant 153 | Else{ 154 | $Compliance = "Firewall Non-Compliant" 155 | Write-Log "Firewall not setup correctly " 156 | Write-Log "Firewall Check Returned - $Compliance" 157 | Return $Compliance 158 | } 159 | 160 | Write-Log "BC Firewall netsh Check Returned - $Compliance" 161 | 162 | #========================================================= 163 | # Call netsh to carry out a match against the status 164 | #========================================================= 165 | $ShowHttpUrl = netsh http show url 166 | # Checking the port has been set - for both listen and connect ports 167 | $BCUrlRes = $myvar = [bool]($ShowHttpUrl | Select-String -SimpleMatch -Pattern "http://+:$BCPort/116B50EB-ECE2-41ac-8429-9F9E963361B7/") 168 | $BCListenPortReg = ((Get-ItemProperty -path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PeerDist\DownloadManager\Peers\Connection' -Name ListenPort -ErrorAction SilentlyContinue).ListenPort) -eq $BCPort 169 | $BCConnectPortReg = ((Get-ItemProperty -path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PeerDist\DownloadManager\Peers\Connection' -Name ConnectPort -ErrorAction SilentlyContinue).ConnectPort) -eq $BCPort 170 | 171 | if($BCUrlRes -eq $true -and $BCListenPortReg -eq $true -and $BCConnectPortReg -eq $true){ 172 | $Compliance = "Compliant" 173 | } 174 | else{ 175 | $Compliance = "BranchCache Port Non-Compliant" 176 | Write-Log "BC Service correct Listening or Connect Port not set" 177 | Write-Log "BC Port Check Returned - $Compliance" 178 | Return $Compliance 179 | } 180 | 181 | Write-Log "BC Port Check Returned - $Compliance" 182 | 183 | #========================================================= 184 | #Next Check that the BranchCache Cache TTL is set correctly 185 | #========================================================= 186 | 187 | if((Get-ItemProperty -path 'HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion\PeerDist\Retrieval' -Name SegmentTTL -ErrorAction SilentlyContinue).SegmentTTL -eq $TTL){ 188 | $Compliance = "Compliant" 189 | } 190 | else{ 191 | $Compliance = "BC Cache TTL Non-Compliant" 192 | Write-Log "BC Cache TTL not setup correctly" 193 | Write-Log "BC Cache TTL Check Returned - $Compliance" 194 | Return $Compliance 195 | } 196 | 197 | Write-Log "BC Cache TTL Check Returned - $Compliance" 198 | 199 | #========================================================= 200 | # Next Check that the BranchCache service is enabled and set to Distributed Caching 201 | #========================================================= 202 | 203 | # Call netsh to carry out a match against the status 204 | $ShowStatusCommand = {netsh branchcache show status} 205 | $ShowStatus = Invoke-Command -ScriptBlock $ShowStatusCommand 206 | $ShowStatusMsg = $ShowStatus | Out-String 207 | WRite-Log "netsh output:" 208 | Write-Log $ShowStatusMsg 209 | # Checking status - if the previous check for BC Cache TTL was Compliant AND the service is setup correctly - we're OK 210 | if((@($ShowStatus | Select-String -SimpleMatch -Pattern "Distributed Caching")[0].ToString() -match "Distributed Caching") -and ($Compliance -eq "Compliant")){ 211 | 212 | $Compliance = "Compliant" 213 | } 214 | else{ 215 | $Compliance = "BC Mode Non-Compliant" 216 | Write-Log "BC Service not setup correctly " 217 | Write-Log "BC Svc Distributed Mode Check Returned - $Compliance" 218 | Return $Compliance 219 | } 220 | 221 | Write-Log "BC Svc Distributed Mode Check Returned - $Compliance" 222 | 223 | #========================================================= 224 | # Next Check the BranchCache SERVE ON BATTERY is set to your preferred setting 225 | #========================================================= 226 | 227 | switch ($ServeOnBattery){ 228 | TRUE {$ServeOnBattery = 1} 229 | FALSE{$ServeOnBattery = 0} 230 | } 231 | 232 | if((Get-ItemProperty -path 'HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion\PeerDist\DownloadManager\Upload' -Name ServePeersOnBatteryPower -ErrorAction SilentlyContinue).ServePeersOnBatteryPower -eq $ServeOnBattery){ 233 | $Compliance = "Compliant" 234 | } 235 | else{ 236 | $Compliance = "BC Battery Mode Non-Compliant" 237 | Write-Log "BC Serve Peers on Battery not setup correctly " 238 | Write-Log "BC Battery Check Mode Check Returned - $Compliance" 239 | Return $Compliance 240 | } 241 | 242 | Write-Log "BC Battery Check Mode Check Returned - $Compliance" 243 | 244 | #========================================================= 245 | # Finally check the branchcache service is started and is set to auto-start 246 | #========================================================= 247 | $s = gwmi -Query "Select State, StartMode From Win32_Service Where Name='peerdistsvc'" 248 | 249 | if (($s.StartMode -eq "Auto") -and ($s.State -eq "Running")){ 250 | $Compliance = "Compliant" 251 | } 252 | else{ 253 | $Compliance = " BC Svc State Non-Compliant" 254 | Write-Log "BC Service not set to Autostart " 255 | Write-Log "BC Svc startup Check Returned - $Compliance" 256 | Return $Compliance 257 | } 258 | 259 | Write-Log "BC Svc startup Check Returned - $Compliance" 260 | Return $Compliance 261 | -------------------------------------------------------------------------------- /Verify BranchCache Flags on Packages, Software Updates, Applications and Deployments/Set-BranchCache-TaskSequences.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script is used to enable, disable, and get BranchCache information about TaskSequences in the environment 4 | 5 | When run in Gather Mode - The script returns a count of the TaskSequence deployments that are NOT branchCache enabled. 6 | 7 | When run in Enable mode - The script enables TaskSequence deployments for BranchCache. 8 | 9 | When run in Disable mode the script disables TaskSequences deployments for BranchCachee. 10 | 11 | .DESCRIPTION 12 | This script is used to enable, disable, and get BranchCache information about TaskSequences in the environment 13 | 14 | .NOTES 15 | This script is used to enable, disable, and get BranchCache information about TaskSequences in the environment 16 | Just edit the Sitecode, mode, and optional application name (wild card is supported) 17 | 18 | This script was mostly 'stolen with pride' from examples all over the place but specific thanks to @NickolajA, @david_obrien and @merlin_with_a_j ! 19 | 20 | Use at your own risk - and test it first! 21 | 22 | Be aware that updating deployments will trigger a policy update. 23 | 24 | FileName: Set-BranchCache-Packages.ps1 25 | Authors: Phil Wilcock, Jordan Benzing, and Johan Arwidmark ('Stolen with pride' by Maik Koster) 26 | Contact: @2PintSoftware 27 | Created: 2022-01-14 28 | Modified: 2022-01-14 29 | 30 | Version - 0.0.0 - (UNKNOWN) 31 | Version - 0.1.0 - (14-JANUARY-2022) 32 | COMPLETED: Converted copy of Set-BranchCachePackages to work with Task Sequences (Maik Koster) 33 | 34 | 35 | .PARAMETER SiteServer 36 | This parameter is a string and is designed to only accept the name of the ConfigMgr site server. This information is 37 | then used to gather the other required information. 38 | 39 | .PARAMETER Mode 40 | This parameter is a string parameter and requires you to pick from a validation set. The available modes are: 41 | Gather - Only returns the information about the not BranchCache enabled TaskSequences 42 | Enable - Enables the TaskSequence deplyoments that are not using it. 43 | Disable - Disables all of them. 44 | 45 | .EXAMPLE 46 | .\Set-BranchCache-TaskSequences.PS1 -siteServer "ServerName" -Mode Gather 47 | 48 | This example would gather all of the TaskSequence deployments that are not currently BranchCache enabled. 49 | 50 | .EXAMPLE 51 | .\Set-BranchCache-TaskSequences.PS1 -siteServer "ServerName" -Mode Enable 52 | 53 | This example would gather all TaskSequences deployments that are not BranchCache enabled and then enable them. Caution enabling this. 54 | 55 | .EXAMPLE 56 | .\Set-BranchCache-TaskSequences.PS1 -siteServer "ServerName" 57 | 58 | This example would gather all of the TaskSequence deployments that are not currently BranchCache enabled. 59 | 60 | #> 61 | 62 | [cmdletbinding()] 63 | param( 64 | [Parameter(HelpMessage = "Please enter the name of your site server" , Mandatory = $true )] 65 | [string]$SiteServer, 66 | [Parameter(HelpMessage = "This option allows you to enable BranchCache for all task sequences. By Default we only return the BranchCache enabled task sequences.",Mandatory = $false)] 67 | [ValidateSet('Enable','Gather','Disable')] 68 | [string]$Mode = "Gather", 69 | [Parameter(HelpMessage = "This option allows you to enable BranchCache for a specific task sequence.",Mandatory = $false)] 70 | [Alias("TSName")] 71 | [string]$TaskSequenceName 72 | ) 73 | 74 | begin{ 75 | #Region helperfunctions 76 | function Get-CMModule { 77 | #This function gets the configMgr module 78 | [CmdletBinding()] 79 | param() 80 | 81 | try { 82 | Write-Verbose "Attempting to import SCCM Module" 83 | Import-Module (Join-Path $(Split-Path $ENV:SMS_ADMIN_UI_PATH) ConfigurationManager.psd1) -Verbose:$false 84 | Write-Verbose "Succesfully imported the SCCM Module" 85 | } catch { 86 | Throw "Failure to import SCCM Cmdlets." 87 | } 88 | } 89 | 90 | function Test-ConfigMgrAvailable { 91 | #Tests if ConfigMgr is availble so that the SMSProvider and configmgr cmdlets can help. 92 | [CmdletBinding()] 93 | param ( 94 | [Parameter(Mandatory = $false)] 95 | [bool]$Remediate 96 | ) 97 | 98 | try { 99 | #Check to see if the Configuration Manager module is loaded or not and then since the remediate flag is set automatically imports it. 100 | if ((Test-Module -ModuleName ConfigurationManager -Remediate:$true) -eq $false) { 101 | throw "You have not loaded the configuration manager module please load the appropriate module and try again." 102 | #Throws this error if even after the remediation or if the remediation fails. 103 | } 104 | Write-Verbose "ConfigurationManager Module is loaded" 105 | Write-Verbose "Checking if current drive is a CMDrive" 106 | if ((Get-Location -Verbose:$false).Path -ne (Get-location -PSProvider 'CmSite' -Verbose:$false).Path) { 107 | #Checks if the current location is the - PS provider for the CMSite server. 108 | Write-Verbose -Message "The location is NOT currently the CMDrive" 109 | if ($Remediate) { 110 | Write-Verbose -Message "Remediation was requested now attempting to set location to the the CM PSDrive" 111 | Set-Location -Path (((Get-PSDrive -PSProvider CMSite -Verbose:$false).Name) + ":") -Verbose:$false 112 | Write-Verbose -Message "Succesfully connected to the CMDrive" 113 | } else { 114 | throw "You are not currently connected to a CMSite Provider Please Connect and try again" 115 | } 116 | } 117 | Write-Verbose "Succesfully validated connection to a CMProvider" 118 | return $true 119 | } catch { 120 | $errorMessage = $_.Exception.Message 121 | Write-Error -Exception CMPatching -Message $errorMessage 122 | return $false 123 | } 124 | } 125 | 126 | function Test-Module { 127 | #Function that is designed to test a module if it is loaded or not. 128 | [CmdletBinding()] 129 | param ( 130 | [Parameter(Mandatory = $true)] 131 | [String]$ModuleName, 132 | [Parameter(Mandatory = $false)] 133 | [bool]$Remediate 134 | ) 135 | 136 | if (Get-Module -Name $ModuleName) { 137 | Write-Verbose -Message "The module was already loaded return TRUE" 138 | return $true 139 | } else { 140 | Write-Verbose -Message "The Module was not already loaded evaluate if remediation flag was set" 141 | if($Remediate -eq $true) { 142 | try { 143 | if($ModuleName -eq "ConfigurationManager") { 144 | Write-Verbose -Message "Non-Standard module requested run pre-written function" 145 | Get-CMModule 146 | #Runs the command to get the COnfigMgr module if its needed. 147 | Write-Verbose -Message "Succesfully loaded the module" 148 | return $true 149 | } else { 150 | Write-Verbose -Message "Remediation flag WAS set now attempting to import module $($ModuleName)" 151 | Import-Module -Name $ModuleName 152 | #Import the other module as needed - if they have no custom requirements. 153 | Write-Verbose -Message "Succesfully improted the module $ModuleName" 154 | Return $true 155 | } 156 | } catch { 157 | Write-Error -Message "Failed to import the module $($ModuleName)" 158 | Set-Location $StartingLocation 159 | break 160 | } 161 | } else { 162 | Return $false 163 | } 164 | } 165 | } 166 | #endregion HelperFunctions 167 | 168 | #region LogFunctions 169 | 170 | Function Start-Log { 171 | #Set global variable for the write-log function in this session or script. 172 | [CmdletBinding()] 173 | param ( 174 | #[ValidateScript({ Split-Path $_ -Parent | Test-Path })] 175 | [string]$FilePath 176 | ) 177 | 178 | try { 179 | if (!(Split-Path $FilePath -Parent | Test-Path)) { 180 | New-Item (Split-Path $FilePath -Parent) -Type Directory | Out-Null 181 | } 182 | #Confirm the provided destination for logging exists if it doesn't then create it. 183 | if (!(Test-Path $FilePath)) { 184 | ## Create the log file destination if it doesn't exist. 185 | New-Item $FilePath -Type File | Out-Null 186 | } 187 | ## Set the global variable to be used as the FilePath for all subsequent write-log 188 | ## calls in this session 189 | $global:ScriptLogFilePath = $FilePath 190 | } catch { 191 | #In event of an error write an exception 192 | Write-Error $_.Exception.Message 193 | } 194 | } 195 | 196 | Function Write-Log { 197 | #Write the log file if the global variable is set 198 | param ( 199 | [Parameter(Mandatory = $true)] 200 | [string]$Message, 201 | [Parameter()] 202 | [ValidateSet(1, 2, 3)] 203 | [string]$LogLevel=1, 204 | [Parameter(Mandatory = $false)] 205 | [bool]$writetoscreen = $true 206 | ) 207 | 208 | $TimeGenerated = "$(Get-Date -Format HH:mm:ss).$((Get-Date).Millisecond)+000" 209 | $Line = '' 210 | $LineFormat = $Message, $TimeGenerated, (Get-Date -Format MM-dd-yyyy), "$($MyInvocation.ScriptName | Split-Path -Leaf):$($MyInvocation.ScriptLineNumber)", $LogLevel 211 | $Line = $Line -f $LineFormat 212 | [system.GC]::Collect() 213 | Add-Content -Value $Line -Path $global:ScriptLogFilePath 214 | if($writetoscreen) { 215 | switch ($LogLevel) { 216 | '1'{ 217 | Write-Verbose -Message $Message 218 | } 219 | '2'{ 220 | Write-Warning -Message $Message 221 | } 222 | '3'{ 223 | Write-Error -Message $Message 224 | } 225 | Default { 226 | } 227 | } 228 | } 229 | 230 | if($writetolistbox -eq $true) { 231 | $result1.Items.Add("$Message") 232 | } 233 | } 234 | 235 | function set-DefaultLogPath { 236 | #Function to set the default log path if something is put in the field then it is sent somewhere else. 237 | [CmdletBinding()] 238 | param ( 239 | [parameter(Mandatory = $false)] 240 | [bool]$defaultLogLocation = $true, 241 | [parameter(Mandatory = $false)] 242 | [string]$LogLocation 243 | ) 244 | 245 | if ($defaultLogLocation) { 246 | $LogPath = Split-Path $script:MyInvocation.MyCommand.Path 247 | $LogFile = "$($($script:MyInvocation.MyCommand.Name).Substring(0,$($script:MyInvocation.MyCommand.Name).Length-4)).log" 248 | Start-Log -FilePath $($LogPath + "\" + $LogFile) 249 | } else { 250 | $LogPath = $LogLocation 251 | $LogFile = "$($($script:MyInvocation.MyCommand.Name).Substring(0,$($script:MyInvocation.MyCommand.Name).Length-4)).log" 252 | Start-Log -FilePath $($LogPath + "\" + $LogFile) 253 | } 254 | } 255 | 256 | #endregion LogFunctions 257 | } 258 | 259 | process{ 260 | Set-DefaultLogPath 261 | $StartingLocation = Get-Location 262 | # Set BitValue to detect BranchCache 263 | $BrachCacheBitValue = 65536 #0x00010000 264 | if (!(Test-ConfigMgrAvailable -Remediate:$true)) { 265 | Write-Log -Message "We were unable to load the ConfigMgr Cmdlets and unable to connect to the CM provider. Exiting ..." -LogLevel 3 266 | Set-Location -Path $StartingLocation 267 | break 268 | } 269 | 270 | Write-Log -Message "The '$($Mode)' option was selected" -LogLevel 1 271 | Write-Log -Message "Now retrieving the task sequence deployments and analyzing them" 272 | if($TaskSequenceName){ 273 | Write-Log -Message "You have selected to only process the task sequence '$($TaskSequenceName)' " 274 | $TaskSequences = Get-CMTaskSequence -Verbose:$False -Name $TaskSequenceName -Fast 275 | if($TaskSequences -eq $null){ 276 | Write-Log -Message "The task sequence was not found. Exiting ..." -LogLevel 3 277 | Set-Location -Path $StartingLocation 278 | break 279 | } 280 | foreach ($object in $TaskSequences) { 281 | Write-Log -Message "Found $($Object.Name) with ID - $($Object.PackageID)" 282 | } 283 | $Advertisements = $TaskSequences | Foreach-Object {Get-CMTaskSequenceDeployment -Fast -Verbose:$false -TaskSequenceID $_.PackageID} 284 | if($Advertisements -eq $null){ 285 | Write-Log -Message "You have selected a task sequence that does not have any applicable advertisements. Exiting..." -LogLevel 3 286 | Set-Location -Path $StartingLocation 287 | break 288 | } 289 | } else { 290 | Write-Log -Message "You have selected to process all task sequences with deployments" -LogLevel 1 291 | $TaskSequences = Get-CMTaskSequence -Verbose:$False -fast 292 | $Advertisements = Get-CMTaskSequenceDeployment -Fast -Verbose:$false | Sort-Object $_.PackageID 293 | if($Advertisements -eq $null){ 294 | Write-Log -Message "There are no task sequences that have applicable advertisments. Exiting..." -LogLevel 3 295 | Set-Location -Path $StartingLocation 296 | break 297 | } 298 | } 299 | 300 | $BranchCacheInfo = New-Object System.Collections.ArrayList($null) 301 | ForEach ($Advertisement in $Advertisements) { 302 | [int]$currentItem = [array]::indexof($Advertisements,$Advertisement) 303 | $TaskSequence = $TaskSequences | Where-Object {$_.PackageID -eq $Advertisement.PackageID} 304 | Write-Log -Message "Now processing $($CurrentItem + 1)/$($Advertisements.Count) - '$($TaskSequence.Name)' ($($Advertisement.PackageID)) - DeploymentID:$($Advertisement.AdvertisementID)" 305 | $Info = [ordered]@{ 306 | BranchCacheState = ($Advertisement.AdvertFlags -band $BrachCacheBitValue)/$BrachCacheBitValue 307 | DeploymentID = $Advertisement.AdvertisementID 308 | CollectionID = $Advertisement.CollectionID 309 | PackageID = $Advertisement.PackageID 310 | TaskSequenceName = $TaskSequence.Name 311 | } 312 | $NewBranchCacheInfo = New-Object PSObject -Property $Info 313 | $BranchCacheInfo.Add($NewBranchCacheInfo) | Out-Null 314 | 315 | if($NewBranchCacheInfo.BranchCacheState -eq '0'){ 316 | if (($Mode -ieq "Gather") -or ($Mode -ieq "Disable")) { 317 | Write-Log -message "The deployment $($Advertisement.AdvertisementID) for task sequence '$($NewBranchCacheInfo.TaskSequenceName)' is not enabled for BranchCache" 318 | } elseif ($Mode -ieq "Enable") { 319 | Write-Log -message "Enabling the deployment $($Advertisement.AdvertisementID) for task sequence '$($NewBranchCacheInfo.TaskSequenceName)' for BranchCache" 320 | # Need to fall back to plain WMI and handling the flags ourselves, as the Set-CMTaskSequenceDeployment contains a bug 321 | # where AllowSharedContent does not set the correct flag. 322 | # Make sure the Advertisement is still up-to-date 323 | $Advertisement.Get() 324 | # Set the new AdvertFlags 325 | $Advertisement.AdvertFlags = $Advertisement.AdvertFlags -bor $BrachCacheBitValue 326 | $Advertisement.Put() 327 | 328 | # Keep correct command in case the Bug gets fixed 329 | #Set-CMTaskSequenceDeployment -CollectionID $Advertisement.CollectionID -TaskSequencePackageId $Advertisement.PackageId -AllowSharedContent $true -Verbose:$false 330 | } 331 | } else { 332 | if (($Mode -ieq "Gather") -or ($Mode -ieq "Enable")) { 333 | Write-Log -message "The deployment $($Advertisement.AdvertisementID) for task sequence '$($NewBranchCacheInfo.TaskSequenceName)' is enabled for BranchCache" 334 | } elseif ($Mode -ieq "disable"){ 335 | Write-Log -message "Disabling the deployment $($Advertisement.AdvertisementID) for task sequence '$($NewBranchCacheInfo.TaskSequenceName)' for BranchCache" 336 | # Need to fall back to plain WMI and handling the flags ourselves, as the Set-CMTaskSequenceDeployment contains a bug 337 | # where AllowSharedContent does not set the correct flag. 338 | # Make sure the Advertisement is still up-to-date 339 | $Advertisement.Get() 340 | # Set the new AdvertFlags 341 | $Advertisement.AdvertFlags = $Advertisement.AdvertFlags -band -bnot $BrachCacheBitValue 342 | $Advertisement.Put() 343 | 344 | # Keep correct command in case the Bug gets fixed 345 | # Set-CMTaskSequenceDeployment -CollectionID $Advertisement.CollectionID -TaskSequencePackageId $Advertisement.PackageId -AllowSharedContent $false -Verbose:$false 346 | } 347 | } 348 | } 349 | 350 | if($Mode -ieq "Gather"){ 351 | Write-Log -Message "Total of $($BranchCacheInfo.Count) Task Sequence Deployments" -LogLevel 1 352 | write-log -Message "$($Count = ($BranchCacheInfo | Where-Object {$_.BranchCacheState -eq '0'}).Count; if($Count -eq $null){$Count = "1";$Count}else{$Count}) Deployments are NOT BranchCache enabled" 353 | Write-Log -Message "$($Count = ($BranchCacheInfo | Where-Object {$_.BranchCacheState -eq '1'}).Count; if($Count -eq $null){$Count = "1";$Count}else{$count}) Deployments ARE Enabled" 354 | Write-Log -Message "Finalized the information. Returning the task sequence deployments that need to be processed to the screen." 355 | Write-OutPut -InputObject $($BranchCacheInfo | Where-Object {$_.BranchCacheState -eq '0'} | ft ) 356 | } 357 | 358 | Set-Location -Path $StartingLocation 359 | } -------------------------------------------------------------------------------- /Verify BranchCache Flags on Packages, Software Updates, Applications and Deployments/Set-BranchCache-SoftwareUpdates.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script is used to enable, disable, and get BranchCache information about the number of software update deployments in the environment. 4 | 5 | When run in Gather Mode - The script returns a count of the update deployments that are NOT BranchCache enabled. 6 | 7 | When run in ENABLE mode - The script enables ALL update deployments for BranchCache. 8 | 9 | When run in Disable mode the script DISABLES all update deployments for BranchCache. 10 | 11 | .DESCRIPTION 12 | This script is used to enable, disable, and get BranchCache information about software update deployments in the environment. 13 | It enable BranchCache for all future deployments that are created by the currently existing ADRS. 14 | 15 | .NOTES 16 | This script is used to enable, disable, and get BranchCache information about software update deployments in the environment. 17 | 18 | This script was mostly 'stolen with pride' from examples all over the place but specific thanks to 19 | @NickolajA, @david_obrien and @merlin_with_a_j ! 20 | 21 | Use at your own risk - and test it first! 22 | 23 | Be aware that updating deployments will trigger a policy update. 24 | 25 | FileName: Set-BranchCache-SoftwareUpdates.ps1 26 | Authors: Phil Wilcock, Jordan Benzing, and Johan Arwidmark 27 | Contact: @2PintSoftware 28 | Created: 2019-07-02 29 | Modified: 2019-07-02 30 | 31 | Version - 0.0.0 - (UNKNOWN) 32 | Version - 0.1.0 - (03-JULY-2019) 33 | COMPLETED: Added in function and helper logic for all of the logging functions 34 | COMPLETED: Added in switch logic for the Enable Disable and Gather steps 35 | COMPLETED: Basic Functionality Check 36 | COMPLETED: Create functional Progress Bars to track (Spends a LONG time on these steps where you don't know what its doing) 37 | COMPLETED: Clean up script logging as we go along 38 | COMPLETED: Updated the Parameter and example information 39 | COMPLETED: Forgot to Add the Input object from the Loops. 40 | COMPLETED: Add Warning Message before making changes 41 | 42 | 43 | 44 | .PARAMETER SiteServer 45 | This parameter is a string and is designed to only accept the name of the ConfigMgr site server. This information is 46 | then used to gather the other required information. 47 | 48 | .PARAMETER Mode 49 | This parameter is a string parameter and requires you to pick from a validation set. The available modes are: 50 | Gather - Only returns the information about the not BranchCache enabled software updates 51 | Enable - Enables the updates that are not using it. 52 | Disable - Disables all of them. 53 | 54 | .EXAMPLE 55 | .\Set-BranchCache-SoftwareUpdates.PS1 -siteServer "ServerName" 56 | 57 | This example would gather all deployments that are not BranchCache enabled for software updates 58 | 59 | .EXAMPLE 60 | .\Set-BranchCache-SoftwareUpdates.PS1 -siteServer "ServerName" -Mode Gather 61 | 62 | This example would gather all deployments that are not BranchCache enabled for software updates 63 | 64 | .EXAMPLE 65 | .\Set-BranchCache-SoftwareUpdates.PS1 -siteServer "ServerName" -Mode Enable 66 | 67 | This example would gather all deployments that are not BranchCache enabled for software updates and ENABLE them 68 | 69 | .EXAMPLE 70 | .\Set-BranchCache-SoftwareUpdates.PS1 -siteServer "ServerName" -Mode Disable 71 | 72 | This example would gather all deployments that are not BranchCache enabled for software updates and DISABLE them 73 | #> 74 | 75 | [cmdletbinding()] 76 | param( 77 | [Parameter(HelpMessage = "Please enter the name of your site server" , Mandatory = $true )] 78 | [string]$SiteServer, 79 | [Parameter(HelpMessage = "This option allows you to enable BranchCache for all packages. By Default we only return the BranchCache enabled packages.",Mandatory = $false)] 80 | [ValidateSet('Enable','Gather','Disable')] 81 | [string]$Mode = "Gather" 82 | ) 83 | 84 | begin{ 85 | #Region helperfunctions 86 | function Get-CMModule 87 | #This function gets the configMgr module 88 | { 89 | [CmdletBinding()] 90 | param() 91 | Try 92 | { 93 | Write-Verbose "Attempting to import SCCM Module" 94 | #Retrieves the fcnction from ConfigMgr installation path. 95 | Import-Module (Join-Path $(Split-Path $ENV:SMS_ADMIN_UI_PATH) ConfigurationManager.psd1) -Verbose:$false 96 | Write-Verbose "Succesfully imported the SCCM Module" 97 | } 98 | Catch 99 | { 100 | Throw "Failure to import SCCM Cmdlets." 101 | } 102 | } 103 | 104 | function Test-ConfigMgrAvailable 105 | #Tests if ConfigMgr is availble so that the SMSProvider and configmgr cmdlets can help. 106 | { 107 | [CMdletbinding()] 108 | Param 109 | ( 110 | [Parameter(Mandatory = $false)] 111 | [bool]$Remediate 112 | ) 113 | try 114 | { 115 | if((Test-Module -ModuleName ConfigurationManager -Remediate:$true) -eq $false) 116 | #Checks to see if the Configuration Manager module is loaded or not and then since the remediate flag is set automatically imports it. 117 | { 118 | throw "You have not loaded the configuration manager module please load the appropriate module and try again." 119 | #Throws this error if even after the remediation or if the remediation fails. 120 | } 121 | write-Verbose "ConfigurationManager Module is loaded" 122 | Write-Verbose "Checking if current drive is a CMDrive" 123 | if((Get-location -Verbose:$false).Path -ne (Get-location -PSProvider 'CmSite' -Verbose:$false).Path) 124 | #Checks if the current location is the - PS provider for the CMSite server. 125 | { 126 | Write-Verbose -Message "The location is NOT currently the CMDrive" 127 | if($Remediate) 128 | #If the remediation field is set then it attempts to set the current location of the path to the CMSite server path. 129 | { 130 | Write-Verbose -Message "Remediation was requested now attempting to set location to the the CM PSDrive" 131 | Set-Location -Path (((Get-PSDrive -PSProvider CMSite -Verbose:$false).Name) + ":") -Verbose:$false 132 | Write-Verbose -Message "Succesfully connected to the CMDrive" 133 | #Sets the location properly to the PSDrive. 134 | } 135 | 136 | else 137 | { 138 | throw "You are not currently connected to a CMSite Provider Please Connect and try again" 139 | } 140 | } 141 | write-Verbose "Succesfully validated connection to a CMProvider" 142 | return $true 143 | } 144 | catch 145 | { 146 | $errorMessage = $_.Exception.Message 147 | write-error -Exception CMPatching -Message $errorMessage 148 | return $false 149 | } 150 | } 151 | 152 | function Test-Module 153 | #Function that is designed to test a module if it is loaded or not. 154 | { 155 | [CMdletbinding()] 156 | Param 157 | ( 158 | [Parameter(Mandatory = $true)] 159 | [String]$ModuleName, 160 | [Parameter(Mandatory = $false)] 161 | [bool]$Remediate 162 | ) 163 | If(Get-Module -Name $ModuleName) 164 | #Checks if the module is currently loaded and if it is then return true. 165 | { 166 | Write-Verbose -Message "The module was already loaded return TRUE" 167 | return $true 168 | } 169 | If((Get-Module -Name $ModuleName) -ne $true) 170 | #Checks if the module is NOT loaded and if it's not loaded then check to see if remediation is requested. 171 | { 172 | Write-Verbose -Message "The Module was not already loaded evaluate if remediation flag was set" 173 | if($Remediate -eq $true) 174 | #If the remediation flag is selected then attempt to import the module. 175 | { 176 | try 177 | { 178 | if($ModuleName -eq "ConfigurationManager") 179 | #If the module requested is the Configuration Manager module use the below method to try to import the ConfigMGr Module. 180 | { 181 | Write-Verbose -Message "Non-Standard module requested run pre-written function" 182 | Get-CMModule 183 | #Runs the command to get the COnfigMgr module if its needed. 184 | Write-Verbose -Message "Succesfully loaded the module" 185 | return $true 186 | } 187 | else 188 | { 189 | Write-Verbose -Message "Remediation flag WAS set now attempting to import module $($ModuleName)" 190 | Import-Module -Name $ModuleName 191 | #Import the other module as needed - if they have no custom requirements. 192 | Write-Verbose -Message "Succesfully improted the module $ModuleName" 193 | Return $true 194 | } 195 | } 196 | catch 197 | { 198 | Write-Error -Message "Failed to import the module $($ModuleName)" 199 | Set-Location $StartingLocation 200 | break 201 | } 202 | } 203 | else { 204 | #Else return the fact that it's not applicable and return false from the execution. 205 | { 206 | Return $false 207 | } 208 | } 209 | } 210 | } 211 | #endregion HelperFunctions 212 | 213 | #region LogFunctions 214 | 215 | Function Start-Log 216 | #Set global variable for the write-log function in this session or script. 217 | { 218 | [CmdletBinding()] 219 | param ( 220 | #[ValidateScript({ Split-Path $_ -Parent | Test-Path })] 221 | [string]$FilePath 222 | ) 223 | try 224 | { 225 | if(!(Split-Path $FilePath -Parent | Test-Path)) 226 | { 227 | New-Item (Split-Path $FilePath -Parent) -Type Directory | Out-Null 228 | } 229 | #Confirm the provided destination for logging exists if it doesn't then create it. 230 | if (!(Test-Path $FilePath)) 231 | { 232 | ## Create the log file destination if it doesn't exist. 233 | New-Item $FilePath -Type File | Out-Null 234 | } 235 | ## Set the global variable to be used as the FilePath for all subsequent write-log 236 | ## calls in this session 237 | $global:ScriptLogFilePath = $FilePath 238 | } 239 | catch 240 | { 241 | #In event of an error write an exception 242 | Write-Error $_.Exception.Message 243 | } 244 | } 245 | 246 | Function Write-Log 247 | #Write the log file if the global variable is set 248 | { 249 | param ( 250 | [Parameter(Mandatory = $true)] 251 | [string]$Message, 252 | [Parameter()] 253 | [ValidateSet(1, 2, 3)] 254 | [string]$LogLevel=1, 255 | [Parameter(Mandatory = $false)] 256 | [bool]$writetoscreen = $true 257 | ) 258 | $TimeGenerated = "$(Get-Date -Format HH:mm:ss).$((Get-Date).Millisecond)+000" 259 | $Line = '' 260 | $LineFormat = $Message, $TimeGenerated, (Get-Date -Format MM-dd-yyyy), "$($MyInvocation.ScriptName | Split-Path -Leaf):$($MyInvocation.ScriptLineNumber)", $LogLevel 261 | $Line = $Line -f $LineFormat 262 | [system.GC]::Collect() 263 | Add-Content -Value $Line -Path $global:ScriptLogFilePath 264 | if($writetoscreen) 265 | { 266 | switch ($LogLevel) 267 | { 268 | '1'{ 269 | Write-Verbose -Message $Message 270 | } 271 | '2'{ 272 | Write-Warning -Message $Message 273 | } 274 | '3'{ 275 | Write-Error -Message $Message 276 | } 277 | Default { 278 | } 279 | } 280 | } 281 | if($writetolistbox -eq $true) 282 | { 283 | $result1.Items.Add("$Message") 284 | } 285 | } 286 | 287 | function set-DefaultLogPath 288 | { 289 | #Function to set the default log path if something is put in the field then it is sent somewhere else. 290 | [CmdletBinding()] 291 | param 292 | ( 293 | [parameter(Mandatory = $false)] 294 | [bool]$defaultLogLocation = $true, 295 | [parameter(Mandatory = $false)] 296 | [string]$LogLocation 297 | ) 298 | if($defaultLogLocation) 299 | { 300 | $LogPath = Split-Path $script:MyInvocation.MyCommand.Path 301 | $LogFile = "$($($script:MyInvocation.MyCommand.Name).Substring(0,$($script:MyInvocation.MyCommand.Name).Length-4)).log" 302 | Start-Log -FilePath $($LogPath + "\" + $LogFile) 303 | } 304 | else 305 | { 306 | $LogPath = $LogLocation 307 | $LogFile = "$($($script:MyInvocation.MyCommand.Name).Substring(0,$($script:MyInvocation.MyCommand.Name).Length-4)).log" 308 | Start-Log -FilePath $($LogPath + "\" + $LogFile) 309 | } 310 | } 311 | 312 | #endregion LogFunctions 313 | } 314 | 315 | process{ 316 | set-DefaultLogPath 317 | Write-Log -Message "Now starting all logs for the duration of the script" -LogLevel 1 318 | $StartingLocation = Get-Location 319 | Write-Log -Message "Set the starting location and stored it to return" -LogLevel 1 320 | Write-Log -Message "Now Confirming that ConfigMgr is available and if it's not we will error out" -LogLevel 1 321 | if(!(Test-ConfigMgrAvailable -Remediate:$true)){ 322 | Write-Log -Message "We were unable to load the ConfigMgr Cmdlets and unable to connect to the CM provider will now exit." -LogLevel 3 323 | break 324 | } 325 | #Deal with the fast warnings 326 | $CMPSSuppressFastNotUsedCheckStartState = $CMPSSuppressFastNotUsedCheck 327 | $CMPSSuppressFastNotUsedCheck = $true 328 | 329 | #Run if the Gather Option is called 330 | if($Mode -ieq "Gather"){ 331 | Write-Log -Message "Now retrieving the BranchCache enabled deployments" -LogLevel 1 332 | $BCEnabledDeployments = Get-CMSoftwareUpdateDeployment -Verbose:$false | Where-Object { $_.UseBranchCache -eq $true }| Select-Object AssignmentName 333 | Write-Log -Message "Now retrieving the BranchCache disabled deployments" 334 | $BCDisabledDeployments = Get-CMSoftwareUpdateDeployment -Verbose:$false | Where-Object { $_.UseBranchCache -eq $false } | Select-Object AssignmentName 335 | Write-Log -Message "Total Software Updates Deployments: $($BCEnabledDeployments.Count + $BCDisabledDeployments.count)" 336 | Write-Log -Message "BranchCache Enabled Software Updates Deployments: $($BCEnabledDeployments.Count)" 337 | Write-Log -Message "BranchCache Disabled Software Updates Deployments: $($BCDisabledDeployments.Count)" 338 | Set-Location -Path $StartingLocation 339 | } 340 | #Fun if the Enable Option is called 341 | if($Mode -ieq "Enable"){ 342 | Write-Log -Message "Now preparing to ENABLE the software update groups for BranchCache deployment." 343 | $CMSUPDeployments = Get-CMSoftwareUpdateDeployment -Verbose:$false | Where-Object {$_.UseBranchCache -eq $false} 344 | $CMSUPADRS = Get-CMSoftwareUpdateAutoDeploymentRule -Verbose:$false 345 | $CMSUPADRDeployments = Get-CMSoftwareUpdateAutoDeploymentRuleDeployment -Verbose:$false 346 | 347 | 348 | ForEach($Deployment in $CMSUPDeployments){ 349 | [int]$currentItem = [array]::indexof($CMSUPDeployments,$Deployment) 350 | Write-Progress -Activity "Enabling Software Update Deplyoments for BranchCache" -Status "Currently Enabling - $($Deployment.AssignmentName) - ($($CurrentItem + 1) of $($CMSUPDeployments.Count)) $([math]::round((($currentItem + 1)/($CMSUPDeployments.Count + 1)),2) * 100)% " -PercentComplete $([float](($currentItem + 1)/($CMSUPDeployments.Count + 1)) * 100) 351 | Set-CMSoftwareUpdateDeployment -Verbose:$false -UseBranchCache $true -inputobject $Deployment 352 | Write-Log -Message "Enabled $($Deployment.AssignmentName) for BranchCache" -LogLevel 1 353 | } 354 | 355 | ForEach($ADR in $CMSUPADRS){ 356 | [int]$currentItem = [array]::indexof($CMSUPADRS,$ADR) 357 | Write-Progress -Activity "Enabling Software Update ADR for BranchCache" -Status "Currently Enabling - $($ADR.Name) - ($($CurrentItem + 1) of $($CMSUPADRS.Count)) $([math]::round((($currentItem + 1)/($CMSUPADRS.Count + 1)),2) * 100)% " -PercentComplete $([float](($currentItem + 1)/($CMSUPADRS.Count + 1)) * 100) 358 | Set-CMSoftwareUpdateAutoDeploymentRule -Verbose:$false -UseBranchCache $true -inputobject $ADR 359 | Write-Log -Message "Enabled $($ADR.Name) for BranchCache" -LogLevel 1 360 | } 361 | 362 | 363 | ForEach($ADRDeployment in $CMSUPADRDeployments){ 364 | [int]$currentItem = [array]::indexof($CMSUPADRDeployments,$ADRDeployment) 365 | Write-Progress -Activity "Enabling Software Update ADR DEPLOYMENT for BranchCache" -Status "Currently Enabling the deployment targeting - $($ADRADRDeployment.CollectionName) - ($($CurrentItem + 1) of $($CMSUPADRDeployments.Count)) $([math]::round((($currentItem + 1)/($CMSUPADRDeployments.Count + 1)),2) * 100)% " -PercentComplete $([float](($currentItem + 1)/($CMSUPADRDeployments.Count + 1)) * 100) 366 | Set-CMSoftwareUpdateAutoDeploymentRuleDeployment -Verbose:$false -UseBranchCache $true -inputobject $ADRDeployment 367 | Write-Log -Message "Enabled the ADR Deployment rule targeting collection $($ADRDeployment.CollectionName) for BranchCache" -LogLevel 1 368 | } 369 | Set-Location -Path $StartingLocation 370 | } 371 | #If the disable option is called. 372 | if($Mode -ieq "Disable"){ 373 | Write-Log -Message "Now preparing to DISABLE the software update groups for BranchCache deployment." 374 | $CMSUPDeployments = Get-CMSoftwareUpdateDeployment -Verbose:$false | Where-Object {$_.UseBranchCache -eq $true} 375 | $CMSUPADRS = Get-CMSoftwareUpdateAutoDeploymentRule -Verbose:$false 376 | $CMSUPADRDeployments = Get-CMSoftwareUpdateAutoDeploymentRuleDeployment -Verbose:$false 377 | 378 | ForEach($Deployment in $CMSUPDeployments){ 379 | [int]$currentItem = [array]::indexof($CMSUPDeployments,$Deployment) 380 | Write-Progress -Activity "Disabling Software Update Deplyoments for BranchCache" -Status "Currently Disabling - $($Deployment.AssignmentName) - ($($CurrentItem + 1) of $($CMSUPDeployments.Count)) $([math]::round((($currentItem + 1)/($CMSUPDeployments.Count + 1)),2) * 100)% " -PercentComplete $([float](($currentItem + 1)/($CMSUPDeployments.Count + 1)) * 100) 381 | Set-CMSoftwareUpdateDeployment -Verbose:$false -UseBranchCache $false -inputobject $Deployment 382 | Write-Log -Message "DISABLED $($Deployment.AssignmentName) for BranchCache" -LogLevel 1 383 | } 384 | 385 | 386 | ForEach($ADR in $CMSUPADRS){ 387 | [int]$currentItem = [array]::indexof($CMSUPADRS,$ADR) 388 | Write-Progress -Activity "Enabling Software Update ADR for BranchCache" -Status "Currently DISABLING - $($ADR.Name) - ($($CurrentItem + 1) of $($CMSUPADRS.Count)) $([math]::round((($currentItem + 1)/($CMSUPADRS.Count + 1)),2) * 100)% " -PercentComplete $([float](($currentItem + 1)/($CMSUPADRS.Count + 1)) * 100) 389 | Set-CMSoftwareUpdateAutoDeploymentRule -Verbose:$false -UseBranchCache $false -inputobject $ADR 390 | Write-Log -Message "DISABLED $($ADR.Name) for BranchCache" -LogLevel 1 391 | } 392 | 393 | ForEach($ADRDeployment in $CMSUPADRDeployments){ 394 | [int]$currentItem = [array]::indexof($CMSUPADRDeployments,$ADRDeployment) 395 | Write-Progress -Activity "Enabling Software Update ADR DEPLOYMENT for BranchCache" -Status "Currently DISABLING the deployment targeting - $($ADRADRDeployment.CollectionName) - ($($CurrentItem + 1) of $($CMSUPADRDeployments.Count))) $([math]::round((($currentItem + 1)/($CMSUPADRDeployments.Count + 1)),2) * 100)% " -PercentComplete $([float](($currentItem + 1)/($CMSUPADRDeployments.Count + 1)) * 100) 396 | Set-CMSoftwareUpdateAutoDeploymentRuleDeployment -Verbose:$false -UseBranchCache $false -inputobject $ADRDeployment 397 | Write-Log -Message "DISABLED the ADR Deployment rule targeting collection $($ADRDeployment.CollectionName) for BranchCache" -LogLevel 1 398 | } 399 | 400 | Set-Location -Path $StartingLocation 401 | } 402 | #Reset the fast warnings. 403 | $CMPSSuppressFastNotUsedCheck = $CMPSSuppressFastNotUsedCheckStartState 404 | } -------------------------------------------------------------------------------- /CacheInjection/InjectBCData.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Takes the content of a folder and injects it into the local BranchCache Cache 4 | 5 | .DESCRIPTION 6 | Takes the content from a target folder, and injects each file into the BranchCache cache 7 | Only injects files over 64k 8 | This and a lot of our scripts rely on BranchCacheTool.exe which can be found here for download: 9 | https://2pintsoftware.com/products/free-tool-branchcachetool/ 10 | 11 | 12 | .NOTES 13 | AUTHOR: 2Pint Software 14 | EMAIL: support@2pintsoftware.com 15 | VERSION: 1.0.0.5 16 | 02/04/2019 17 | 18 | CHANGE LOG: 19 | 20 | 1.0.0.1 : 02/01/2019 : Initial version of script - after fixing all the bugs in the AH version of course.. :) 21 | 1.0.0.2 : 05/03/2019 : Added the /BufferSize switch as this makes a BIG difference to CPU usage.. 22 | 1.0.0.3 : 06/03/2019 : Cleaned up all the PW hard codes, changed sleep logic and input parameters 23 | 1.0.0.4 : 07/03/2019 : Added optional progressbar and force returncode of 0 at the end 24 | 1.0.0.5 : 02/04/2019 : Improved logic for server secret to allow dodgy characters 25 | 1.0.0.6 : 30/12/2019 : Fix for wrong error handling 26 | 1.0.0.7 : 19/08/2020 : Fixed error handling for TSProgressUI when it has failed to register 27 | 28 | .USAGE .\InjectBCData.ps1 29 | -Path (mandatory) Path to the folder containing the files that you want to inject 30 | -ServerSecret (mandatory) this is the server secret of the BranchCache SERVER (a DP if you are using SCCM) 31 | it's in the reg - HKLM\SOFTWARE\Microsoft\SMS\DP - BranchCacheKey 32 | Use the folllowing PowerShell command to get from your server as hex string: 33 | [System.BitConverter]::ToString((Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PeerDist\SecurityManager\Restricted\' -Name Seed).Seed).Replace("-","") 34 | -Recurse Default $true - do you want to include files in subfolders? 35 | -GenerateV1 Default $False - on a Windows 10 client you can serve content to Win7 clients if you set this to $true 36 | as we can generate Windows 7 hashes BUT it requires DOUBLE the disk space in the cache 37 | -DeleteSourceFiles Default $False - delete source files once injected 38 | -Logfile - Defaults to the current folder, make sure you set this if the media is read only 39 | -BufferSize - Default 8000 (Bytes) - Sets the buffersize for feeding the BC API 40 | - 8000 is a 'safe' setting, avoiding excessing CPU usage. 16000 or higher results in higher CPU/Disk use. 41 | 42 | -UseTmpForCI default $true - set this to $true to use the %TMP% folder for CI creation, use for read only media 43 | -SleepBase default 1000 - pause between the creation of files to keep CPU utilization down, the default value of 1000 is typically enough, set to 0 to disable. 44 | -ShowProgress default $false - set to $true to use the ConfigMgr task sequence progressbar, can only be used in a Task Sequence 45 | 46 | 47 | .EXAMPLE 48 | -Path .. -Logfile %tmp%\inject.log -ServerSecret %SERVERSECRET% -BufferSize 128000 -UseTmpForCI $true -ShowProgress $true -SleepBase 0 # Use this command line for read only media TS injections 49 | -Path C:\Temmp\MyFolders -Logfile %tmp%\inject.log -ServerSecret 540F1914AAF90BDC30E94EA797C65FF4E844BAC337854A5073EEB77A81D55A72 -BufferSize 8192 -SleepBase 1000 # Use this command line for injection with low CPU 50 | 51 | .LINK 52 | https://2pintsoftware.com 53 | #> 54 | [CmdletBinding()] 55 | Param( 56 | [Parameter()][bool]$GenerateV1=$False, 57 | [Parameter()][bool]$Recurse=$true, 58 | [Parameter()][ValidateSet("SilentlyContinue", "Continue")][string]$DebugPreference, 59 | [Parameter(Mandatory=$true)][string]$Path, 60 | [Parameter()][bool]$DeleteSourceFiles=$false, 61 | [Parameter()][string]$Logfile = "$PSScriptRoot\BCInjector.log", 62 | [Parameter(Mandatory=$true)][string]$ServerSecret, 63 | [Parameter()][int64]$BufferSize=8000, 64 | [Parameter()][bool]$UseTmpForCI=$false, 65 | [Parameter()][int]$SleepBase = 1000, 66 | [Parameter()][bool]$ShowProgress=$false 67 | ) 68 | #Uncomment this if you want to do some timing 69 | $stopwatch = [system.diagnostics.stopwatch]::StartNew() 70 | 71 | 72 | #======================================= 73 | # Injector FUNCTIONS 74 | #======================================= 75 | Function TimeStamp {$(Get-Date -UFormat %T)} 76 | 77 | 78 | Function StringToBytes 79 | { 80 | [CmdletBinding()] 81 | Param( 82 | [ValidateNotNullOrEmpty()] 83 | [Parameter(ValueFromPipeline=$True,Mandatory=$True)] 84 | [string]$strInput 85 | ) 86 | 87 | $strInput = $strInput.Replace("-",""); 88 | $strInput = $strInput.Replace(":",""); 89 | 90 | try 91 | { 92 | $bytes = [System.Byte[]]::CreateInstance([System.Byte],$strInput.Length/2); 93 | 94 | [int] $i = 0; 95 | [int] $x = 0; 96 | 97 | while ($strInput.Length -gt $i) 98 | { 99 | $lngDecimal = [System.Convert]::ToInt32($strInput.Substring($i, 2), 16); 100 | $bytes[$x] = [System.Convert]::ToByte($lngDecimal); 101 | $i = $i + 2; 102 | ++$x; 103 | } 104 | 105 | return $bytes; 106 | 107 | } 108 | catch 109 | { 110 | Write-Error "Failed to parse valid secret as bytes, check command line. The failing secret is:$strInput" 111 | return 0; 112 | } 113 | } 114 | 115 | 116 | Function New-CI 117 | { 118 | [CmdletBinding()] 119 | Param( 120 | [ValidateNotNullOrEmpty()] 121 | [ValidateScript({(Test-Path $_) -and ((Get-Item $_).Length -gt 64kb)})] 122 | [Parameter(ValueFromPipeline=$True,Mandatory=$True)] 123 | [string]$FilePath, 124 | [ValidateNotNullOrEmpty()] 125 | [Parameter(ValueFromPipeline=$True,Mandatory=$True)] 126 | [string]$CIPath, 127 | [Parameter(Mandatory=$false)][ValidateSet("V1", "V2")][string]$CIVersion, 128 | [int]$MsecSleepBase = 1000 129 | ) 130 | 131 | Begin 132 | {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function started"} 133 | 134 | Process 135 | { 136 | Write-Verbose "$($MyInvocation.MyCommand.Name):: Processing file: $Filepath" 137 | #Inserted a sleep to try to lower CPU hit when processing 138 | 139 | 140 | $Guid = [guid]::NewGuid(); 141 | 142 | $args = @("/PublishCI", "/InputDataFile", "$FilePath","/ContentID","$Guid", "/OutputCIFile", "$CIPath", "/CIVersion", "$CIVersion", "/Quiet") 143 | 144 | Write-Debug "Executing $exe with arguments $args" 145 | 146 | #Genereate the CI with BranchCacheTool.exe 147 | &$exe $args 148 | 149 | $(TimeStamp) + " : Finished Generation of CI file: $CIPath " | Out-File $Logfile -Append 150 | Write-Verbose "$($MyInvocation.MyCommand.Name):: Finished Generation of file: $CIPath" 151 | 152 | $fileprop = Get-ItemProperty -Path $FilePath 153 | if($MsecSleepBase -gt 0) 154 | { 155 | [int]$millisec_sleep_base = $MsecSleepBase; 156 | [int]$sleep_add_per_file_size = $fileprop.Length / 1024 /1024 * 5; 157 | [int]$sleeptime_msec = $millisec_sleep_base + $sleep_add_per_file_size; 158 | 159 | Write-Verbose "$($MyInvocation.MyCommand.Name):: Sleeping for: $sleeptime_msec milliseconds" 160 | Start-Sleep -Milliseconds $sleeptime_msec 161 | } 162 | 163 | 164 | return $CIPath 165 | } 166 | 167 | End 168 | {Write-Verbose "$($MyInvocation.MyCommand.Name):: CI Function ended"} 169 | } 170 | 171 | function Add-BCData 172 | { 173 | [CmdletBinding()] 174 | Param( 175 | [ValidateNotNullOrEmpty()] 176 | [ValidateScript({(Test-Path $_) -and ((Get-Item $_).Length -gt 64kb)})] 177 | [Parameter(ValueFromPipeline=$True,Mandatory=$True)] 178 | [string]$FilePath, 179 | [ValidateNotNullOrEmpty()] 180 | [ValidateScript({(Test-Path $_) -and ((Get-Item $_).Length -gt 0)})] 181 | [Parameter(ValueFromPipeline=$True,Mandatory=$True)] 182 | [string]$CIPath 183 | ) 184 | 185 | Begin 186 | {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function started"} 187 | 188 | Process 189 | { 190 | Write-Verbose "$($MyInvocation.MyCommand.Name):: Processing file: $Filepath with CI: $CIPath" 191 | $(TimeStamp) + " : Processing file: $Filepath with CI: $CIPath" | Out-File $Logfile -Append 192 | if(Test-Path -Path $CIPath) 193 | { 194 | $CIContent = Get-Item -Path $CIPath 195 | if($CIContent.Length -gt 0) 196 | { 197 | #We have data in both files, inject it 198 | $args = @("/AddData","/BufferSizeBytes", $Buffersize, "/InputDataFile", "$FilePath", "/InputCIFile", "$CIPath", "/Quiet") 199 | 200 | Write-Debug "Executing $exe with arguments $args" 201 | &$exe $args 202 | 203 | } 204 | else 205 | { 206 | $(TimeStamp) + " : Warning! Missing file data in CI! $CIPath" | Out-File $Logfile -Append 207 | Write-Warning "Missing file data in $CIPath" 208 | } 209 | } 210 | $(TimeStamp) + " : Finished Processing file: $FilePath" | Out-File $Logfile -Append 211 | Write-Verbose "$($MyInvocation.MyCommand.Name):: Finished Processing file: $FilePath" 212 | 213 | } 214 | 215 | End 216 | {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function ended"} 217 | } 218 | 219 | function InjectData 220 | { 221 | [CmdletBinding()] 222 | Param( 223 | [ValidateNotNullOrEmpty()] 224 | [ValidateScript({(Test-Path $_) -and ((Get-Item $_).Length -gt 64kb)})] 225 | [Parameter(ValueFromPipeline=$true,Mandatory=$true)] 226 | [string]$FilePath, 227 | [Parameter()][bool]$DeleteCI = $true, 228 | [Parameter()][bool]$DeleteFile = $true, 229 | [Parameter()][bool]$GenerateV1 = $false, 230 | [Parameter()][bool]$UseTmpForCI = $false, 231 | [Parameter()][int]$SleepBase = 1000 232 | ) 233 | 234 | Begin 235 | {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function started"} 236 | 237 | Process 238 | { 239 | Write-Verbose "$($MyInvocation.MyCommand.Name):: Processing file: $Filepath" 240 | 241 | #Genereate the CI with BranchCacheTool.exe 242 | 243 | if($UseTmpForCI -eq $true) 244 | { 245 | $CIGuid = [guid]::NewGuid(); 246 | $CIPath = "$env:tmp" + "\$CIGuid" + ".ci"; 247 | } 248 | else 249 | { 250 | $CIPath = $FilePath + ".ci"; 251 | } 252 | 253 | if(($GenerateV1 -eq $true) -or (($GenerateV1 -eq $False) -and ($V2Capable -eq $False))) 254 | { 255 | $CIVersion = "V1"; 256 | } 257 | else 258 | { 259 | $CIVersion = "V2" 260 | } 261 | $(TimeStamp) + " : CI Version is $CIVersion" | Out-File $Logfile -Append 262 | Write-Debug "CI Version is $CIVersion" 263 | $CIPath = $CIPath + ".$CIVersion" 264 | Write-Debug "Calling function New-CI -FilePath $FilePath -CIPath $CIPath -MsecSleepBase $SleepBase" 265 | 266 | $return = New-CI -FilePath $FilePath -CIPath $CIPath -CIVersion $CIVersion -MsecSleepBase $SleepBase 267 | 268 | Write-Debug "We have a CI path, now we verify the file: $CIPath" 269 | 270 | if(Test-Path -Path $CIPath) 271 | { 272 | $CIContent = Get-Item -Path $CIPath 273 | if($CIContent.Length -gt 0) 274 | { 275 | Write-Debug "We have a CI, now we generate the data from the CI: $CIPath" 276 | Add-BCData -FilePath $FilePath -CIPath $CIPath 277 | $(TimeStamp) + " : Injected $FilePath into the BranchCache Cache, delete the CI:$DeleteCI" | Out-File $Logfile -Append 278 | Write-Output "Injected $FilePath into the BranchCache Cache, delete the CI:$DeleteCI" 279 | 280 | If ($DeleteCI){Write-Debug "Delete the CI file: $CIPath" 281 | Remove-Item $CIPath} 282 | If ($DeleteFile){Write-Debug "Delete the file: $FilePath" 283 | Remove-Item $FilePath} 284 | } 285 | else 286 | { 287 | $(TimeStamp) + " : Missing file data in $CIPath" | Out-File $Logfile -Append 288 | Write-Warning "Missing file data in $CIPath" 289 | } 290 | } 291 | else 292 | { 293 | $(TimeStamp) + " : Warning!Missing CI file: $CIPath" | Out-File $Logfile -Append 294 | Write-Warning "Missing CI file: $CIPath" 295 | } 296 | 297 | Write-Verbose "$($MyInvocation.MyCommand.Name):: Finished Processing file: $FilePath" 298 | 299 | } 300 | 301 | End 302 | {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function ended"} 303 | } 304 | 305 | if(!$PSScriptRoot){ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent} 306 | 307 | $exe = "$PSScriptRoot\BranchCacheTool.exe" 308 | $netsh = "$Env:windir\System32\netsh.exe" 309 | 310 | If (Test-Path $Logfile){ri $Logfile -Force -ErrorAction SilentlyContinue -Confirm:$false} 311 | If (!(Test-Path $Logfile)){New-Item -ItemType File -Force -Path $Logfile -ErrorAction SilentlyContinue -Confirm:$false} 312 | 313 | 314 | 315 | #check OS version - if Win10 we can do V2 content 316 | $V2Capable = $false 317 | $OS = [Environment]::OSVersion 318 | if(($OS.Version.Major -gt 6) -or (($OS.Version.Major -eq 6) -and ($OS.Version.Minor -ge 2))) 319 | { 320 | $V2Capable = $true 321 | } 322 | 323 | #Check for BranchCacheTool.exe as we need that 324 | $checkEXE = Get-Item -Path $exe -ErrorAction SilentlyContinue 325 | 326 | if(!$checkEXE.Exists) 327 | { 328 | $(TimeStamp) + " : Fail - seems BranchCacheTool.exe is missing" | Out-File $Logfile -Append 329 | Write-Error "Fail - seems BranchCacheTool.exe is missing" 330 | Return 331 | } 332 | 333 | Write-Debug "Made it so far! Target Content Path is: $Path"; 334 | 335 | #Check the path to the content is valid 336 | if(!(Test-Path -Path $Path)) 337 | { 338 | $(TimeStamp) + " : Error - Target Folder does not exist!" | Out-File $Logfile -Append 339 | Write-Error "Error - Target Folder does not exist!" 340 | Return 341 | } 342 | else 343 | { 344 | Write-Debug "Content folder exists...let's continue" 345 | } 346 | 347 | #====================================== 348 | # SET The Server Secret to same as the CM DP 349 | # And backup the old one - this will be reset once the migration is complete 350 | #======================================= 351 | $netsh = "$Env:windir\System32\netsh.exe" 352 | if($ServerSecret -ne "") 353 | { 354 | $(TimeStamp) + " : Reading the current BranchCache secret key" | Out-File $Logfile -Append 355 | Write-Debug "Reading the current BranchCache secret key" 356 | 357 | $secrethex = [System.BitConverter]::ToString((Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PeerDist\SecurityManager\Restricted\' -Name Seed).Seed).Replace("-","") 358 | $OldKey = StringToBytes($secrethex); 359 | 360 | if($OldKey.Length -eq 0) 361 | { 362 | Write-Error "Could not parse current secret key from registry"; 363 | $(TimeStamp) + " : Could not parse current secret key from registry" | Out-File $Logfile -Append 364 | return; 365 | } 366 | 367 | $(TimeStamp) + " : Changing the Server Secret for the injection to: $ServerSecret" | Out-File $Logfile -Append 368 | Write-Debug "Changing the Server Secret for the injection to: $ServerSecret" 369 | $NewKey = StringToBytes($ServerSecret); 370 | if($NewKey.Length -lt 2) 371 | { 372 | Write-Error "Could not parse secret key from command line"; 373 | $(TimeStamp) + " : Could not parse secret key from command line" | Out-File $Logfile -Append 374 | return; 375 | } 376 | 377 | 378 | Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PeerDist\SecurityManager\Restricted\' -Name Seed -Value ([byte[]]$NewKey); 379 | Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PeerDist\SecurityManager\Restricted\' -Name SeedBackup -Value ([byte[]]$OldKey); 380 | } 381 | 382 | 383 | #======================================= 384 | # End Server Secret Setup 385 | #======================================= 386 | 387 | #Now we can get the files to process (note slightly different cmd depending on OS version) 388 | If ($recurse -eq $true) {$action = 'Recurse' 389 | $params = @{ $action = $true }} 390 | Else {$action = 'Recurse' 391 | $params = @{ $action = $false }} 392 | 393 | 394 | $directory = Get-Item $Path 395 | if ($V2Capable) 396 | { 397 | $files = @($directory | Get-ChildItem -File @params) 398 | } 399 | else 400 | { 401 | #W7 version - Get-ChildItem doesn't support the -file parameter 402 | $files = @($directory | Get-ChildItem @params | Where-Object { !$_.PSIsContainer }) 403 | } 404 | 405 | #check that there is actual content - if not - quit 406 | if($files.Length -gt 0) 407 | { 408 | Write-Debug " Adding files:" 409 | $(TimeStamp) + " : Adding Files of size: " + $files.Length | Out-File $Logfile -Append 410 | } 411 | else 412 | { 413 | Write-Warning "No data to inject! Exiting" 414 | $(TimeStamp) + " : No data to inject! Exiting" | Out-File $Logfile -Append 415 | Return 416 | } 417 | 418 | #===================== 419 | # UI Progress 420 | #===================== 421 | $UseTSPrg = $false; 422 | 423 | if($ShowProgress -eq $true) 424 | { 425 | try 426 | { 427 | $TsEnv = New-Object -ComObject Microsoft.SMS.TSEnvironment 428 | 429 | try 430 | { 431 | $TSPrg = New-Object -ComObject Microsoft.SMS.TSProgressUI 432 | $UseTSPrg = $true; 433 | } 434 | catch 435 | { 436 | Write-Warning "Unable to connect to the Task Sequence Progress UI! Please verify you are in a running Task Sequence Environment. Please note: TSProgressUI cannot be loaded during a prestart command.`n`nErrorDetails:`n$_" 437 | #Reset to avoid flooding the log file 438 | $ShowProgress = $false 439 | } 440 | } 441 | catch 442 | { 443 | #We throw and exit if we are not part of a TS 444 | throw "Unable to connect to the Task Sequence Environment! Please verify you are in a running Task Sequence Environment.`n`nErrorDetails:`n$_" 445 | } 446 | } 447 | 448 | 449 | 450 | #===================== 451 | #Main lifting of files 452 | #===================== 453 | #If we are on Win10 but $GenerateV1 is set to $true we will gen a V1 hash for each file 454 | #!!!This doubles the disk requirement!!! 455 | If ($DeleteSourceFiles -eq $true) {$action = 'DeleteFile' 456 | $params = @{ $action = $true }} 457 | Else {$action = 'DeleteFile' 458 | $params = @{ $action = $false }} 459 | $Step = 0; 460 | [UInt32]$Steps = $files.Length; 461 | [UInt64]$V2DataInjected = 0; 462 | [UInt64]$V1DataInjected = 0; 463 | 464 | Try 465 | { 466 | if(($GenerateV1 -eq $true) -and ($V2Capable -eq $true)) 467 | { 468 | Write-Debug "We are also generating Version 1 content, lets start with that" 469 | foreach ($file in $files) 470 | { 471 | $Step++; 472 | If($file.Length -gt 64kb) 473 | { 474 | 475 | if(($UseTSPrg -eq $true) -and ($ShowProgress -eq $true)) 476 | { 477 | $TSPrg.ShowActionProgress( $TsEnv.Value("_SMSTSOrgName"), $TsEnv.Value("_SMSTSPackageName"), $TsEnv.Value("_SMSTSCustomProgressDialogMessage"),$TsEnv.Value("_SMSTSCurrentActionName"), [Convert]::ToUInt32( $TsEnv.Value("_SMSTSNextInstructionPointer")), [Convert]::ToUInt32($TsEnv.Value("_SMSTSInstructionTableSize")),` 478 | "Injecting file $Step of $Steps : $file (Size: " + ($file.Length/1MB).ToString(".00") +" MB)",` 479 | $Step,` 480 | $files.Length) 481 | } 482 | 483 | $File.fullname | InjectData -DeleteFile $DeleteSourceFiles -GenerateV1 $true -DeleteCI $true -UseTmpForCI $UseTmpForCI -SleepBase $SleepBase 484 | $V1DataInjected = $V1DataInjected + $File.Length; 485 | 486 | } 487 | #Else {ri $File.fullname -Force -ErrorAction SilentlyContinue -Confirm:$false} 488 | } 489 | #we don't delete the source file in this pass as we might still need it 490 | } 491 | } 492 | Catch 493 | { 494 | $(TimeStamp) + "BranchCache Inject Error: " + (Write-Error -Message $_) | Out-File -FilePath $Logfile -Append -Encoding ascii 495 | Return 496 | } 497 | 498 | Try 499 | { 500 | #This generates V1 for Win7 and Gen2 for Gen2 Capable ones 501 | Write-Debug "Generating Version 1 or 2 content, depending on OS Ver" 502 | foreach ($file in $files) 503 | { 504 | $Step++; 505 | If($file.Length -gt 64kb) 506 | { 507 | if(($UseTSPrg -eq $true) -and ($ShowProgress -eq $true)) 508 | { 509 | $TSPrg.ShowActionProgress( $TsEnv.Value("_SMSTSOrgName"), $TsEnv.Value("_SMSTSPackageName"), $TsEnv.Value("_SMSTSCustomProgressDialogMessage"),$TsEnv.Value("_SMSTSCurrentActionName"), [Convert]::ToUInt32( $TsEnv.Value("_SMSTSNextInstructionPointer")), [Convert]::ToUInt32($TsEnv.Value("_SMSTSInstructionTableSize")),` 510 | "Injecting file $Step of $Steps : $file (Size: " + ($file.Length/1MB).ToString(".00") +" MB)",` 511 | $Step,` 512 | $files.Length) 513 | } 514 | 515 | $File.fullname | InjectData -DeleteFile $DeleteSourceFiles -DeleteCI $true -UseTmpForCI $UseTmpForCI -SleepBase $SleepBase 516 | $V2DataInjected = $V2DataInjected + $File.Length; 517 | } 518 | #Else {ri $File.fullname -Force -ErrorAction SilentlyContinue -Confirm:$false} 519 | } 520 | } 521 | Catch 522 | { 523 | $(TimeStamp) + "BranchCache Inject Error: " + (Write-Error -Message $_) | Out-File -FilePath $Logfile -Append -Encoding ascii 524 | Write-Debug "BranchCache Inject Error: " + (Write-Error -Message $_) 525 | Return 526 | } 527 | 528 | 529 | #============================================ 530 | #reset the server secret to the original value 531 | #============================================ 532 | if($ServerSecret -ne "") 533 | { 534 | $(TimeStamp) + " :Resetting the Server Secret after the injection "| Out-File -FilePath $Logfile -Append 535 | Write-Output "Resetting the Server Secret after the injection: $PassPhrase" 536 | Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PeerDist\SecurityManager\Restricted\' -Name Seed -Value ([byte[]]$OldKey); 537 | #&$netsh @("BranchCache", "Set","key","passphrase=$PassPhrase") 538 | 539 | } 540 | 541 | 542 | 543 | $stopwatch.Stop() 544 | $totalSecs = [math]::Round($stopwatch.Elapsed.TotalSeconds,0) 545 | write-host "Completed in $TotalSecs Seconds, injecting $V1DataInjected bytes of V1 content and $V2DataInjected bytes of V2 content" 546 | return 0; 547 | -------------------------------------------------------------------------------- /Verify BranchCache Flags on Packages, Software Updates, Applications and Deployments/Set-BranchCache-Apps.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script is used to enable, disable, and get BranchCache information about applications in the environment 4 | 5 | When run in Gather Mode - The script returns a count of the applications/deployment types that are NOT BranchCache enabled. 6 | 7 | When run in Enable mode - The script enables ALL application deployment types for BranchCache. 8 | 9 | When run in Disable mode the script disables all application deployment types for BranchCache. 10 | 11 | .DESCRIPTION 12 | This script is used to enable, disable, and get BranchCache information about applications in the environment 13 | This script leverages WMI to make these changes as the PowerShell Module does not have a good way to make the changes to the application XML. 14 | 15 | .NOTES 16 | This script is used to enable, disable, and get BranchCache information about applications in the environment 17 | Just edit the Sitecode, mode, and optional application name (wild card is supported) 18 | 19 | This script was mostly 'stolen with pride' from examples all over the place but specific thanks to @NickolajA, @david_obrien and @merlin_with_a_j ! 20 | 21 | Use at your own risk - and test it first! 22 | 23 | Be aware that updating deployments will trigger a policy update. 24 | 25 | FileName: Set-BranchCache-Apps.ps1 26 | Authors: Phil Wilcock, Jordan Benzing, and Johan Arwidmark 27 | Contact: @2PintSoftware 28 | Created: 2019-07-02 29 | Modified: 2019-07-02 30 | 31 | Version - 0.0.0 - (UNKNOWN) 32 | Version - 0.1.0 - (03-JULY-19) 33 | COMPLETED: Add the proper help information 34 | COMPLETED: Add the proper notes and examples 35 | COMPLETED: Enable processing and Begin Blocks 36 | COMPLETED: Add Helper Functions to remove static variables 37 | COMPLETED: Correct all logging to use LOGGS instead 38 | COMPLETED: Enable Verbosity instead of Write-Host everywhere applicable 39 | COMPLETED: Create Progress bars as needed to display status 40 | BUGFIXED: When you only have ONE application the correct count isn't displayed in the progress bar. 41 | COMPLETED: Log the number - of apps without Content - And The state of Cache enable/disable 42 | COMPLETED: Add capability to ONLY target a SINGLE app for enable/disbale/inquire/gather 43 | Version - 0.1.1 - (07-JULY-19) 44 | COMPLETED: - IGNORE apps that do not have content 45 | BUG: - DONT Enable content that's already enabled - To TEST this we have to retrieve all of the deployments for the app any way at that point might as well set it. 46 | COMPLETED: - Allow the user to set the enable state for only ONE named application. 47 | 48 | 49 | 50 | 51 | .PARAMETER SiteServer 52 | This parameter is a string and is designed to only accept the name of the ConfigMgr site server. This information is 53 | thenused to gather the other required information. 54 | 55 | .PARAMETER Mode 56 | This parameter is a string parameter and requires you to pick from a validation set. The available modes are: 57 | Gather - Only returns the information about the not BranchCache enabled app deployment types 58 | Enable - Enables the updates that are not using it. 59 | Disable - Disables all of them. 60 | 61 | .EXAMPLE 62 | .\Set-BranchCache-Apps.PS1 -siteServer "ServerName" -Mode Enable 63 | 64 | This example would gather all application deployment types that are not BranchCache enabled and then enable them. 65 | 66 | .EXAMPLE 67 | .\Set-BranchCache-Apps.PS1 -siteServer "ServerName" -Mode Gather 68 | 69 | This example would gather all of the application deployment types that are not currently BranchCache enabled and display a count of them. 70 | 71 | .EXAMPLE 72 | .\Set-BranchCache-Apps.PS1 -siteServer "ServerName" -Mode DISABLE 73 | 74 | This example would disable BranchCache on ALL application deployment types CAUTION enabling this. 75 | 76 | .EXAMPLE 77 | .\Set-BranchCache-Apps.PS1 -siteServer "ServerName" 78 | 79 | This example would gather all of the application deployment types that are not currently BranchCache enabled and display a count of them. 80 | 81 | #> 82 | 83 | [cmdletbinding()] 84 | param( 85 | [Parameter(HelpMessage = "Please enter the name of your site server" , Mandatory = $true )] 86 | [string]$SiteServer, 87 | [Parameter(HelpMessage = "This option allows you to enable BranchCache for all packages. By Default we only return the BranchCache enabled packages.",Mandatory = $false)] 88 | [ValidateSet('Enable','Gather','Disable')] 89 | [string]$Mode = "Gather", 90 | [Parameter(HelpMessage = "This option allows you to enable BranchCache for a SPECIFIC package",Mandatory = $false)] 91 | [string]$AppName 92 | ) 93 | 94 | begin{ 95 | #Region helperfunctions 96 | function Get-CMModule 97 | #This function gets the configMgr module 98 | { 99 | [CmdletBinding()] 100 | param() 101 | Try 102 | { 103 | Write-Verbose "Attempting to import SCCM Module" 104 | #Retrieves the fcnction from ConfigMgr installation path. 105 | Import-Module (Join-Path $(Split-Path $ENV:SMS_ADMIN_UI_PATH) ConfigurationManager.psd1) -Verbose:$false 106 | Write-Verbose "Succesfully imported the SCCM Module" 107 | } 108 | Catch 109 | { 110 | Throw "Failure to import SCCM Cmdlets." 111 | } 112 | } 113 | 114 | function Test-ConfigMgrAvailable 115 | #Tests if ConfigMgr is availble so that the SMSProvider and configmgr cmdlets can help. 116 | { 117 | [CMdletbinding()] 118 | Param 119 | ( 120 | [Parameter(Mandatory = $false)] 121 | [bool]$Remediate 122 | ) 123 | try 124 | { 125 | if((Test-Module -ModuleName ConfigurationManager -Remediate:$true) -eq $false) 126 | #Checks to see if the Configuration Manager module is loaded or not and then since the remediate flag is set automatically imports it. 127 | { 128 | throw "You have not loaded the configuration manager module please load the appropriate module and try again." 129 | #Throws this error if even after the remediation or if the remediation fails. 130 | } 131 | write-Verbose "ConfigurationManager Module is loaded" 132 | Write-Verbose "Checking if current drive is a CMDrive" 133 | if((Get-location -Verbose:$false).Path -ne (Get-location -PSProvider 'CmSite' -Verbose:$false).Path) 134 | #Checks if the current location is the - PS provider for the CMSite server. 135 | { 136 | Write-Verbose -Message "The location is NOT currently the CMDrive" 137 | if($Remediate) 138 | #If the remediation field is set then it attempts to set the current location of the path to the CMSite server path. 139 | { 140 | Write-Verbose -Message "Remediation was requested now attempting to set location to the the CM PSDrive" 141 | Set-Location -Path (((Get-PSDrive -PSProvider CMSite -Verbose:$false).Name) + ":") -Verbose:$false 142 | Write-Verbose -Message "Succesfully connected to the CMDrive" 143 | #Sets the location properly to the PSDrive. 144 | } 145 | 146 | else 147 | { 148 | throw "You are not currently connected to a CMSite Provider Please Connect and try again" 149 | } 150 | } 151 | write-Verbose "Succesfully validated connection to a CMProvider" 152 | return $true 153 | } 154 | catch 155 | { 156 | $errorMessage = $_.Exception.Message 157 | write-error -Exception CMPatching -Message $errorMessage 158 | return $false 159 | } 160 | } 161 | 162 | function Test-Module 163 | #Function that is designed to test a module if it is loaded or not. 164 | { 165 | [CMdletbinding()] 166 | Param 167 | ( 168 | [Parameter(Mandatory = $true)] 169 | [String]$ModuleName, 170 | [Parameter(Mandatory = $false)] 171 | [bool]$Remediate 172 | ) 173 | If(Get-Module -Name $ModuleName) 174 | #Checks if the module is currently loaded and if it is then return true. 175 | { 176 | Write-Verbose -Message "The module was already loaded return TRUE" 177 | return $true 178 | } 179 | If((Get-Module -Name $ModuleName) -ne $true) 180 | #Checks if the module is NOT loaded and if it's not loaded then check to see if remediation is requested. 181 | { 182 | Write-Verbose -Message "The Module was not already loaded evaluate if remediation flag was set" 183 | if($Remediate -eq $true) 184 | #If the remediation flag is selected then attempt to import the module. 185 | { 186 | try 187 | { 188 | if($ModuleName -eq "ConfigurationManager") 189 | #If the module requested is the Configuration Manager module use the below method to try to import the ConfigMGr Module. 190 | { 191 | Write-Verbose -Message "Non-Standard module requested run pre-written function" 192 | Get-CMModule 193 | #Runs the command to get the COnfigMgr module if its needed. 194 | Write-Verbose -Message "Succesfully loaded the module" 195 | return $true 196 | } 197 | else 198 | { 199 | Write-Verbose -Message "Remediation flag WAS set now attempting to import module $($ModuleName)" 200 | Import-Module -Name $ModuleName 201 | #Import the other module as needed - if they have no custom requirements. 202 | Write-Verbose -Message "Succesfully improted the module $ModuleName" 203 | Return $true 204 | } 205 | } 206 | catch 207 | { 208 | Write-Error -Message "Failed to import the module $($ModuleName)" 209 | Set-Location $StartingLocation 210 | break 211 | } 212 | } 213 | else { 214 | #Else return the fact that it's not applicable and return false from the execution. 215 | { 216 | Return $false 217 | } 218 | } 219 | } 220 | } 221 | #endregion HelperFunctions 222 | 223 | #region LogFunctions 224 | 225 | Function Start-Log 226 | #Set global variable for the write-log function in this session or script. 227 | { 228 | [CmdletBinding()] 229 | param ( 230 | #[ValidateScript({ Split-Path $_ -Parent | Test-Path })] 231 | [string]$FilePath 232 | ) 233 | try 234 | { 235 | if(!(Split-Path $FilePath -Parent | Test-Path)) 236 | { 237 | New-Item (Split-Path $FilePath -Parent) -Type Directory | Out-Null 238 | } 239 | #Confirm the provided destination for logging exists if it doesn't then create it. 240 | if (!(Test-Path $FilePath)) 241 | { 242 | ## Create the log file destination if it doesn't exist. 243 | New-Item $FilePath -Type File | Out-Null 244 | } 245 | ## Set the global variable to be used as the FilePath for all subsequent write-log 246 | ## calls in this session 247 | $global:ScriptLogFilePath = $FilePath 248 | } 249 | catch 250 | { 251 | #In event of an error write an exception 252 | Write-Error $_.Exception.Message 253 | } 254 | } 255 | 256 | Function Write-Log 257 | #Write the log file if the global variable is set 258 | { 259 | param ( 260 | [Parameter(Mandatory = $true)] 261 | [string]$Message, 262 | [Parameter()] 263 | [ValidateSet(1, 2, 3)] 264 | [string]$LogLevel=1, 265 | [Parameter(Mandatory = $false)] 266 | [bool]$writetoscreen = $true 267 | ) 268 | $TimeGenerated = "$(Get-Date -Format HH:mm:ss).$((Get-Date).Millisecond)+000" 269 | $Line = '' 270 | $LineFormat = $Message, $TimeGenerated, (Get-Date -Format MM-dd-yyyy), "$($MyInvocation.ScriptName | Split-Path -Leaf):$($MyInvocation.ScriptLineNumber)", $LogLevel 271 | $Line = $Line -f $LineFormat 272 | [system.GC]::Collect() 273 | Add-Content -Value $Line -Path $global:ScriptLogFilePath 274 | if($writetoscreen) 275 | { 276 | switch ($LogLevel) 277 | { 278 | '1'{ 279 | Write-Verbose -Message $Message 280 | } 281 | '2'{ 282 | Write-Warning -Message $Message 283 | } 284 | '3'{ 285 | Write-Error -Message $Message 286 | } 287 | Default { 288 | } 289 | } 290 | } 291 | if($writetolistbox -eq $true) 292 | { 293 | $result1.Items.Add("$Message") 294 | } 295 | } 296 | 297 | function set-DefaultLogPath 298 | { 299 | #Function to set the default log path if something is put in the field then it is sent somewhere else. 300 | [CmdletBinding()] 301 | param 302 | ( 303 | [parameter(Mandatory = $false)] 304 | [bool]$defaultLogLocation = $true, 305 | [parameter(Mandatory = $false)] 306 | [string]$LogLocation 307 | ) 308 | if($defaultLogLocation) 309 | { 310 | $LogPath = Split-Path $script:MyInvocation.MyCommand.Path 311 | $LogFile = "$($($script:MyInvocation.MyCommand.Name).Substring(0,$($script:MyInvocation.MyCommand.Name).Length-4)).log" 312 | Start-Log -FilePath $($LogPath + "\" + $LogFile) 313 | } 314 | else 315 | { 316 | $LogPath = $LogLocation 317 | $LogFile = "$($($script:MyInvocation.MyCommand.Name).Substring(0,$($script:MyInvocation.MyCommand.Name).Length-4)).log" 318 | Start-Log -FilePath $($LogPath + "\" + $LogFile) 319 | } 320 | } 321 | 322 | #endregion LogFunctions 323 | 324 | #region ScriptFunctions 325 | 326 | #EndRegion ScriptFunctions 327 | 328 | } 329 | 330 | process{ 331 | set-DefaultLogPath 332 | Write-Log -Message "Now starting all logs for the duration of the script" -LogLevel 1 333 | $StartingLocation = Get-Location 334 | Write-Log -Message "Set the starting location and stored it to return" -LogLevel 1 335 | if(!(Test-ConfigMgrAvailable -Remediate:$true)){ 336 | Write-Log -Message "We were unable to load the ConfigMgr Cmdlets and unable to connect to the CM provider will now exit." -LogLevel 3 337 | Set-Location -Path $StartingLocation 338 | break 339 | } 340 | $Sitecode = (Get-PSDrive -PSProvider CMSite -Verbose:$false).Name 341 | 342 | #-------------------------------------- 343 | # Load ConfigMgr application assemblies 344 | #-------------------------------------- 345 | try { 346 | Add-Type -Path (Join-Path -Path (Get-Item $env:SMS_ADMIN_UI_PATH).Parent.FullName -ChildPath "Microsoft.ConfigurationManagement.ApplicationManagement.dll") -ErrorAction Stop 347 | Add-Type -Path (Join-Path -Path (Get-Item $env:SMS_ADMIN_UI_PATH).Parent.FullName -ChildPath "AdminUI.WqlQueryEngine.dll") -ErrorAction Stop 348 | Add-Type -Path (Join-Path -Path (Get-Item $env:SMS_ADMIN_UI_PATH).Parent.FullName -ChildPath "AdminUI.DcmObjectWrapper.dll") -ErrorAction Stop 349 | } 350 | catch [System.UnauthorizedAccessException] { 351 | Write-Warning -Message "Access was denied when attempting to load ApplicationManagement dll's" ; break 352 | } 353 | catch [System.Exception] { 354 | Write-Warning -Message "Unable to load required ApplicationManagement dll's. Make sure that you're running this tool on system where the ConfigMgr console is installed and that you're running the tool elevated" ; break 355 | } 356 | 357 | if ($AppName) { 358 | Write-Log -Message "The option to only return an application with a specific name '$AppName' was selected. Retrieving that application..." 359 | $applications = Get-WmiObject SMS_Application -Computername $SiteServer -Namespace root\sms\site_$SiteCode | Where-Object {$_.IsLatest -and $_.LocalizedDisplayName -like $AppName -and $_.HasContent -eq $true} 360 | if ($Applications -eq $null) { 361 | Write-Log -Message "ERROR - NO application meets the criteria of HAVING content, and having this specific name. Script will exit" -LogLevel 3 362 | Set-Location -Path $StartingLocation 363 | break 364 | } 365 | } else { 366 | Write-Log -Message "The option to return all applications was selected. Retriving all application..." 367 | $Applications = Get-WmiObject SMS_Application -Computername $SiteServer -Namespace root\sms\site_$SiteCode | Where-Object {$_.IsLatest -and $_.HasContent -eq $true} 368 | if ($Applications -eq $null) { 369 | Write-Log -Message "ERROR - NO application meets the criteria of HAVING content. Script will exit" -LogLevel 3 370 | Set-Location -Path $StartingLocation 371 | break 372 | } 373 | } 374 | 375 | if ($Mode -eq "Enable") { 376 | ForEach ($Application in $Applications) { 377 | $Application.Get() 378 | [int]$CurrentItem = [array]::indexof($Applications,$Application) 379 | Write-Progress -Activity "ENABLING BranchCache App Deployment Types for Application ($($CurrentItem + 1) of $(($Applications | Measure-Object).Count)) - $([math]::round((($currentItem + 1)/($Applications.Count + 1)),2) * 100)% " -Status "Currently ENABLING Deployment Types for - $($Application.LocalizedDisplayName)" -PercentComplete $([float](($currentItem + 1)/($Applications.Count + 1)) * 100) 380 | $ApplicationName = $Application.LocalizedDisplayName 381 | $ApplicationXML = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::DeserializeFromString($application.SDMPackageXML) 382 | $UpdateApplication = $false 383 | if ($ApplicationXML.DeploymentTypes -ne $null) { 384 | foreach ($DeploymentType in $ApplicationXML.DeploymentTypes) { 385 | if ($DeploymentType.Installer.Contents.Location -ne $null) { 386 | if ($DeploymentType.Installer.Contents.PeerCache -eq $false) { 387 | Write-Log -Message "$($DeploymentType.Title) : Enabling BranchCache" -LogLevel 1 388 | $DeploymentType.Installer.Contents[0].PeerCache = $true # enable BranchCache 389 | $UpdateApplication = $true 390 | } else { 391 | Write-Log -Message "$($DeploymentType.Title) : BranchCache is enabled" -LogLevel 1 392 | } 393 | } 394 | } 395 | } 396 | if ($UpdateApplication) { 397 | $NewApplicationXml = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::Serialize($ApplicationXML, $false) 398 | $Application.SDMPackageXML = $NewApplicationXml 399 | $Application.Put() | Out-Null 400 | } 401 | } 402 | write-log -message "Done! Happy BranchCache-ing" -LogLevel 1 403 | } 404 | 405 | if ($Mode -eq "Gather") { 406 | $NumberOfBCEnabledApplications = 0 407 | $NumberOfBCDisabledApplications = 0 408 | 409 | ForEach ($Application in $Applications) { 410 | $Application.Get() 411 | [int]$CurrentItem = [array]::indexof($Applications,$Application) 412 | Write-Progress -Activity "GATHERING BranchCache App Deployment Types for Application ($($CurrentItem + 1) of $(($Applications | Measure-Object).Count)) - $([math]::round((($currentItem + 1)/($Applications.Count + 1)),2) * 100)% " -Status "Currently GATHERING Deployment Types for - $($Application.LocalizedDisplayName)" -PercentComplete $([float](($currentItem + 1)/($Applications.Count + 1)) * 100) 413 | $DeploymentTypes = ([Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::DeserializeFromString($application.SDMPackageXML)).DeploymentTypes 414 | if ($DeploymentTypes -ne $null) { 415 | Foreach ($DeploymentType in $DeploymentTypes) { 416 | if ($DeploymentType.Installer.Contents.Location -ne $null) { 417 | if ($DeploymentType.Installer.Contents.PeerCache -eq $True) { 418 | Write-Log -Message "$($DeploymentType.Title) : BranchCache is enabled" -LogLevel 1 419 | $NumberOfBCEnabledApplications++ 420 | } 421 | 422 | if ($DeploymentType.Installer.Contents.PeerCache -eq $False) { 423 | Write-Log "$($DeploymentType.Title) : BranchCache is disabled" 424 | $NumberOfBCDisabledApplications++ 425 | } 426 | } 427 | } 428 | } 429 | } 430 | 431 | Write-Log -Message "Now evaluating Results" -LogLevel 1 432 | Write-Log -Message "Total number of applications: $($NumberOfBCEnabledApplications + $NumberOfBCDisabledApplications)" -LogLevel 1 433 | Write-Log -Message "Total number of applications with content: $($($Applications | Where-Object {$_.HasContent -eq $True} | Measure-Object).Count)" 434 | Write-Log -Message "Total number of applications without Content: $($($Applications | Where-Object {$_.HasContent -eq $False} | Measure-Object).Count)" 435 | Write-Log -Message "Number Of BranchCache Enabled applications: $($NumberOfBCEnabledApplications - $($Applications | Where-Object {$_.HasContent -eq $False} | Measure-Object).Count)" -LogLevel 1 436 | Write-Log -Message "Number Of BranchCache Disabled applications: $($NumberOfBCDisabledApplications)" -LogLevel 1 437 | } 438 | 439 | if ($Mode -eq "Disable") { 440 | ForEach ($Application in $Applications) { 441 | $Application.Get() 442 | [int]$CurrentItem = [array]::indexof($Applications,$Application) 443 | Write-Progress -Activity "DISABLING BranchCache App Deployment Types ($($CurrentItem + 1) of $(($Applications | Measure-Object).Count)) - $([math]::round((($currentItem + 1)/($Applications.Count + 1)),2) * 100)% " -Status "Currently DISABLING BranchCache on Deployment Types for - $($Application.LocalizedDisplayName)" -PercentComplete $([float](($currentItem + 1)/($Applications.Count + 1)) * 100) 444 | $ApplicationName = $Application.LocalizedDisplayName 445 | $ApplicationXML = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::DeserializeFromString($application.SDMPackageXML) 446 | $UpdateApplication = $false 447 | if ($ApplicationXML.DeploymentTypes -ne $null) { 448 | foreach ($DeploymentType in $ApplicationXML.DeploymentTypes) { 449 | if ($DeploymentType.Installer.Contents.Location -ne $null) { 450 | if ($DeploymentType.Installer.Contents.PeerCache -eq $true) { 451 | Write-Log -Message "$($DeploymentType.Title) : Disabling BranchCache" -LogLevel 1 452 | $DeploymentType.Installer.Contents[0].PeerCache = $false # disable BranchCache 453 | $UpdateApplication = $true 454 | } else { 455 | Write-Log -Message "$($DeploymentType.Title) : BranchCache is disabled" -LogLevel 1 456 | } 457 | } 458 | } 459 | } 460 | if ($UpdateApplication) { 461 | $NewApplicationXml = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::Serialize($ApplicationXML, $false) 462 | $Application.SDMPackageXML = $NewApplicationXml 463 | $Application.Put() | Out-Null 464 | } 465 | } 466 | Write-Log -Message "Done! We are sad to see you stop caching" 467 | } 468 | 469 | Set-Location -Path $StartingLocation 470 | } -------------------------------------------------------------------------------- /Verify BranchCache Flags on Packages, Software Updates, Applications and Deployments/Set-BranchCache-Packages.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script is used to enable, disable, and get BranchCache information about packages in the environment 4 | 5 | When run in Gather Mode - The script returns a count of the packages/programs that are NOT branchCache enabled. 6 | 7 | When run in Enable mode - The script enables packages/programs for BranchCache. 8 | 9 | When run in Disable mode the script disables packages/programs for BranchCachee. 10 | 11 | .DESCRIPTION 12 | This script is used to enable, disable, and get BranchCache information about applications in the environment 13 | 14 | .NOTES 15 | This script is used to enable, disable, and get BranchCache information about applications in the environment 16 | Just edit the Sitecode, mode, and optional application name (wild card is supported) 17 | 18 | This script was mostly 'stolen with pride' from examples all over the place but specific thanks to @NickolajA, @david_obrien and @merlin_with_a_j ! 19 | 20 | Use at your own risk - and test it first! 21 | 22 | Be aware that updating deployments will trigger a policy update. 23 | 24 | FileName: Set-BranchCache-Packages.ps1 25 | Authors: Phil Wilcock, Jordan Benzing, and Johan Arwidmark 26 | Contact: @2PintSoftware 27 | Created: 2019-07-02 28 | Modified: 2019-07-02 29 | 30 | Version - 0.0.0 - (UNKNOWN) 31 | Version - 0.1.0 - (03-JULY-2019) 32 | COMPLETED: Converted to obtain site code dynamically 33 | COMPLETED: Converted to use a progress bar to display status 34 | BUGFIXED: Fixed it so that we stopped resetting the location for EVERY program we change 35 | COMPLETED: Add a disable option? 36 | Version 0.1.1 - (07-JULY-2019) 37 | COMPLETED: Add unique package selection 38 | Version 0.1.2 - (15-JULY-2019) 39 | BUGFIXED:- Fixed packages that meet the criteria wildcard to display properly for enable/DISABLE 40 | 41 | 42 | 43 | 44 | .PARAMETER SiteServer 45 | This parameter is a string and is designed to only accept the name of the ConfigMgr site server. This information is 46 | then used to gather the other required information. 47 | 48 | .PARAMETER Mode 49 | This parameter is a string parameter and requires you to pick from a validation set. The available modes are: 50 | Gather - Only returns the information about the not BranchCache enabled software updates 51 | Enable - Enables the updates that are not using it. 52 | Disable - Disables all of them. 53 | 54 | .EXAMPLE 55 | .\Set-BranchCache-Packages.PS1 -siteServer "ServerName" -Mode Enable 56 | 57 | This example would gather all deployments that are not BranchCache enabled and then enable them. 58 | 59 | .EXAMPLE 60 | .\Set-BranchCache-Packages.PS1 -siteServer "ServerName" -Mode Gather 61 | 62 | This example would gather all of the package deployments that are not currently BranchCache enabled. 63 | 64 | .EXAMPLE 65 | .\Set-BranchCache-Packages.PS1 -siteServer "ServerName" 66 | 67 | This example would gather all of the package deployments that are not currently BranchCache enabled. 68 | 69 | .EXAMPLE 70 | .\Set-BranchCache-Packages.PS1 -siteServer "ServerName" -Mode Enable 71 | 72 | This example would enable branchache on ALL package deployments CAUTION enabling this. 73 | 74 | #> 75 | 76 | [cmdletbinding()] 77 | param( 78 | [Parameter(HelpMessage = "Please enter the name of your site server" , Mandatory = $true )] 79 | [string]$SiteServer, 80 | [Parameter(HelpMessage = "This option allows you to enable BranchCache for all packages. By Default we only return the BranchCache enabled packages.",Mandatory = $false)] 81 | [ValidateSet('Enable','Gather','Disable')] 82 | [string]$Mode = "Gather", 83 | [Parameter(HelpMessage = "This option allows you to enable BranchCache for a specific package.",Mandatory = $false)] 84 | [string]$PackageName 85 | ) 86 | 87 | begin{ 88 | #Region helperfunctions 89 | function Get-CMModule 90 | #This function gets the configMgr module 91 | { 92 | [CmdletBinding()] 93 | param() 94 | Try 95 | { 96 | Write-Verbose "Attempting to import SCCM Module" 97 | #Retrieves the fcnction from ConfigMgr installation path. 98 | Import-Module (Join-Path $(Split-Path $ENV:SMS_ADMIN_UI_PATH) ConfigurationManager.psd1) -Verbose:$false 99 | Write-Verbose "Succesfully imported the SCCM Module" 100 | } 101 | Catch 102 | { 103 | Throw "Failure to import SCCM Cmdlets." 104 | } 105 | } 106 | 107 | function Test-ConfigMgrAvailable 108 | #Tests if ConfigMgr is availble so that the SMSProvider and configmgr cmdlets can help. 109 | { 110 | [CMdletbinding()] 111 | Param 112 | ( 113 | [Parameter(Mandatory = $false)] 114 | [bool]$Remediate 115 | ) 116 | try 117 | { 118 | if((Test-Module -ModuleName ConfigurationManager -Remediate:$true) -eq $false) 119 | #Checks to see if the Configuration Manager module is loaded or not and then since the remediate flag is set automatically imports it. 120 | { 121 | throw "You have not loaded the configuration manager module please load the appropriate module and try again." 122 | #Throws this error if even after the remediation or if the remediation fails. 123 | } 124 | write-Verbose "ConfigurationManager Module is loaded" 125 | Write-Verbose "Checking if current drive is a CMDrive" 126 | if((Get-location -Verbose:$false).Path -ne (Get-location -PSProvider 'CmSite' -Verbose:$false).Path) 127 | #Checks if the current location is the - PS provider for the CMSite server. 128 | { 129 | Write-Verbose -Message "The location is NOT currently the CMDrive" 130 | if($Remediate) 131 | #If the remediation field is set then it attempts to set the current location of the path to the CMSite server path. 132 | { 133 | Write-Verbose -Message "Remediation was requested now attempting to set location to the the CM PSDrive" 134 | Set-Location -Path (((Get-PSDrive -PSProvider CMSite -Verbose:$false).Name) + ":") -Verbose:$false 135 | Write-Verbose -Message "Succesfully connected to the CMDrive" 136 | #Sets the location properly to the PSDrive. 137 | } 138 | 139 | else 140 | { 141 | throw "You are not currently connected to a CMSite Provider Please Connect and try again" 142 | } 143 | } 144 | write-Verbose "Succesfully validated connection to a CMProvider" 145 | return $true 146 | } 147 | catch 148 | { 149 | $errorMessage = $_.Exception.Message 150 | write-error -Exception CMPatching -Message $errorMessage 151 | return $false 152 | } 153 | } 154 | 155 | function Test-Module 156 | #Function that is designed to test a module if it is loaded or not. 157 | { 158 | [CMdletbinding()] 159 | Param 160 | ( 161 | [Parameter(Mandatory = $true)] 162 | [String]$ModuleName, 163 | [Parameter(Mandatory = $false)] 164 | [bool]$Remediate 165 | ) 166 | If(Get-Module -Name $ModuleName) 167 | #Checks if the module is currently loaded and if it is then return true. 168 | { 169 | Write-Verbose -Message "The module was already loaded return TRUE" 170 | return $true 171 | } 172 | If((Get-Module -Name $ModuleName) -ne $true) 173 | #Checks if the module is NOT loaded and if it's not loaded then check to see if remediation is requested. 174 | { 175 | Write-Verbose -Message "The Module was not already loaded evaluate if remediation flag was set" 176 | if($Remediate -eq $true) 177 | #If the remediation flag is selected then attempt to import the module. 178 | { 179 | try 180 | { 181 | if($ModuleName -eq "ConfigurationManager") 182 | #If the module requested is the Configuration Manager module use the below method to try to import the ConfigMGr Module. 183 | { 184 | Write-Verbose -Message "Non-Standard module requested run pre-written function" 185 | Get-CMModule 186 | #Runs the command to get the COnfigMgr module if its needed. 187 | Write-Verbose -Message "Succesfully loaded the module" 188 | return $true 189 | } 190 | else 191 | { 192 | Write-Verbose -Message "Remediation flag WAS set now attempting to import module $($ModuleName)" 193 | Import-Module -Name $ModuleName 194 | #Import the other module as needed - if they have no custom requirements. 195 | Write-Verbose -Message "Succesfully improted the module $ModuleName" 196 | Return $true 197 | } 198 | } 199 | catch 200 | { 201 | Write-Error -Message "Failed to import the module $($ModuleName)" 202 | Set-Location $StartingLocation 203 | break 204 | } 205 | } 206 | else { 207 | #Else return the fact that it's not applicable and return false from the execution. 208 | { 209 | Return $false 210 | } 211 | } 212 | } 213 | } 214 | #endregion HelperFunctions 215 | 216 | #region LogFunctions 217 | 218 | Function Start-Log 219 | #Set global variable for the write-log function in this session or script. 220 | { 221 | [CmdletBinding()] 222 | param ( 223 | #[ValidateScript({ Split-Path $_ -Parent | Test-Path })] 224 | [string]$FilePath 225 | ) 226 | try 227 | { 228 | if(!(Split-Path $FilePath -Parent | Test-Path)) 229 | { 230 | New-Item (Split-Path $FilePath -Parent) -Type Directory | Out-Null 231 | } 232 | #Confirm the provided destination for logging exists if it doesn't then create it. 233 | if (!(Test-Path $FilePath)) 234 | { 235 | ## Create the log file destination if it doesn't exist. 236 | New-Item $FilePath -Type File | Out-Null 237 | } 238 | ## Set the global variable to be used as the FilePath for all subsequent write-log 239 | ## calls in this session 240 | $global:ScriptLogFilePath = $FilePath 241 | } 242 | catch 243 | { 244 | #In event of an error write an exception 245 | Write-Error $_.Exception.Message 246 | } 247 | } 248 | 249 | Function Write-Log 250 | #Write the log file if the global variable is set 251 | { 252 | param ( 253 | [Parameter(Mandatory = $true)] 254 | [string]$Message, 255 | [Parameter()] 256 | [ValidateSet(1, 2, 3)] 257 | [string]$LogLevel=1, 258 | [Parameter(Mandatory = $false)] 259 | [bool]$writetoscreen = $true 260 | ) 261 | $TimeGenerated = "$(Get-Date -Format HH:mm:ss).$((Get-Date).Millisecond)+000" 262 | $Line = '' 263 | $LineFormat = $Message, $TimeGenerated, (Get-Date -Format MM-dd-yyyy), "$($MyInvocation.ScriptName | Split-Path -Leaf):$($MyInvocation.ScriptLineNumber)", $LogLevel 264 | $Line = $Line -f $LineFormat 265 | [system.GC]::Collect() 266 | Add-Content -Value $Line -Path $global:ScriptLogFilePath 267 | if($writetoscreen) 268 | { 269 | switch ($LogLevel) 270 | { 271 | '1'{ 272 | Write-Verbose -Message $Message 273 | } 274 | '2'{ 275 | Write-Warning -Message $Message 276 | } 277 | '3'{ 278 | Write-Error -Message $Message 279 | } 280 | Default { 281 | } 282 | } 283 | } 284 | if($writetolistbox -eq $true) 285 | { 286 | $result1.Items.Add("$Message") 287 | } 288 | } 289 | 290 | function set-DefaultLogPath 291 | { 292 | #Function to set the default log path if something is put in the field then it is sent somewhere else. 293 | [CmdletBinding()] 294 | param 295 | ( 296 | [parameter(Mandatory = $false)] 297 | [bool]$defaultLogLocation = $true, 298 | [parameter(Mandatory = $false)] 299 | [string]$LogLocation 300 | ) 301 | if($defaultLogLocation) 302 | { 303 | $LogPath = Split-Path $script:MyInvocation.MyCommand.Path 304 | $LogFile = "$($($script:MyInvocation.MyCommand.Name).Substring(0,$($script:MyInvocation.MyCommand.Name).Length-4)).log" 305 | Start-Log -FilePath $($LogPath + "\" + $LogFile) 306 | } 307 | else 308 | { 309 | $LogPath = $LogLocation 310 | $LogFile = "$($($script:MyInvocation.MyCommand.Name).Substring(0,$($script:MyInvocation.MyCommand.Name).Length-4)).log" 311 | Start-Log -FilePath $($LogPath + "\" + $LogFile) 312 | } 313 | } 314 | 315 | #endregion LogFunctions 316 | } 317 | 318 | process{ 319 | set-DefaultLogPath 320 | Write-Log -Message "Now starting all logs for the duration of the script" -LogLevel 1 321 | $StartingLocation = Get-Location 322 | Write-Log -Message "Set the starting location and stored it to return" -LogLevel 1 323 | Write-Log -Message "Set the bit value to validate if BranchCache is enabled or not" -LogLevel 1 324 | $BitValue = 65536 #0x00010000 325 | Write-Log -Message "Now Confirming that ConfigMgr is available and if it's not we will error out" -LogLevel 1 326 | if(!(Test-ConfigMgrAvailable -Remediate:$true)){ 327 | Write-Log -Message "We were unable to load the ConfigMgr Cmdlets and unable to connect to the CM provider will now exit." -LogLevel 3 328 | Set-Location -Path $StartingLocation 329 | break 330 | } 331 | 332 | if($Mode -ieq "Gather"){ 333 | Write-Log -Message "The GATHER option or the default was selected" -LogLevel 1 334 | Write-Log -Message "Now retrieving the CMPackaged deployments and sorting them" 335 | if($PackageName){ 336 | Write-Log -Message "You have selected to only return a specific package" 337 | $Package = Get-CMPackage -Verbose:$False -Name $PackageName -fast 338 | if($Package -eq $null){ 339 | Write-Log -Message "The package was not found now exiting" -LogLevel 3 340 | Set-Location -Path $StartingLocation 341 | break 342 | } 343 | foreach ($object in $Package) { 344 | write-log -Message "Found $($Object.Name) with ID - $($Object.PackageID)" 345 | } 346 | $Advertisements = $Package | foreach-object{Get-CMPackageDeployment -Verbose:$false -PackageID $_.PackageID | Sort-Object $_.PackageID} 347 | if($Advertisements -eq $null){ 348 | Write-Log -Message "You have selected a package that does not have any applicable advertisements now exiting..." -LogLevel 3 349 | Set-Location -Path $StartingLocation 350 | break 351 | } 352 | } 353 | if(!($PackageName)){ 354 | Write-Log -Message "You have selected to return all packages with deployments" -LogLevel 1 355 | $Packages = Get-CMPackage -Verbose:$False -fast 356 | $Advertisements = Get-CMPackageDeployment -Verbose:$false | Sort-Object $_.PackageID 357 | if($Advertisements -eq $null){ 358 | Write-Log -Message "You have there are no packages that have applicable advertisments now exiting..." -LogLevel 3 359 | Set-Location -Path $StartingLocation 360 | break 361 | } 362 | } 363 | 364 | Write-Log -Message "Generating a list to store all of the retrived information to..." 365 | $list = New-Object System.Collections.ArrayList($null) 366 | Write-Log -Message "Now starting to process and analze all $($Advertisements.Count) deployments..." 367 | ForEach ($Advertisement in $Advertisements) { 368 | [int]$currentItem = [array]::indexof($Advertisements,$Advertisement) 369 | Write-Log -Message "Now processing $($CurrentItem + 1)/$($Advertisements.Count) - $($Advertisement.ProgramName)" 370 | $Info = [ordered]@{ 371 | BranchCacheState = ($Advertisement.AdvertFlags -band $BitValue)/$BitValue 372 | PackageID = $Advertisement.PackageID 373 | ProgramName = $Advertisement.ProgramName 374 | PackageName = $((Get-CMPackage -PackageID $Advertisement.PackageID -fast -Verbose:$False).Name) 375 | } 376 | $Object = New-Object PSObject -Property $Info 377 | $list.add($Object) | Out-Null 378 | if($object.BranchCacheState -eq '0'){ 379 | Write-Log -message "The PackageID: $($object.packageID) - with PackageName: $($Object.PackageName) has a program $($Object.ProgramName) that is not enabled for BranchCache" 380 | } 381 | if($object.BranchCacheState -eq '1'){ 382 | Write-Log -message "The PackageID: $($object.packageID) - with PackageName: $($Object.PackageName) has a program $($Object.ProgramName) is enabled for BranchCache" 383 | } 384 | 385 | } 386 | Write-Log -Message "Total of $($List.Count) Package Deployments" -LogLevel 1 387 | write-log -Message "$($Count = ($List | Where-Object {$_.BranchCacheState -eq '0'}).Count; if($Count -eq $null){$Count = "1";$Count}else{$Count}) Deployments are NOT BranchCache enabled" 388 | Write-Log -Message "$($Count = ($List | Where-Object {$_.BranchCacheState -eq '1'}).Count; if($Count -eq $null){$Count = "1";$Count}else{$count}) Deployments ARE Enabled" 389 | Write-Log -Message "Total number of packages with content: $(($Packages | Where-Object {$_.pkgsourceFlag -eq 2} | Measure-Object).Count)" 390 | Write-Log -Message "Total number of packages without content: $(($Packages | Where-Object {$_.pkgsourceFlag -eq 1} | Measure-Object).Count)" 391 | Write-Log -Message "Finalized the information now returning the objects that need to be processed to the screen." 392 | Write-OutPut -InputObject $($list | Where-Object {$_.BranchCacheState -eq '0'}) 393 | Set-Location -Path $StartingLocation 394 | } 395 | 396 | if($Mode -ieq "Enable"){ 397 | if($PackageName){ 398 | Write-Log -Message "You have selected to only return a specific package" 399 | $Package = Get-CMPackage -Verbose:$False -Name $PackageName -fast 400 | if($Package -eq $null){ 401 | Write-Log -Message "The package was not found now exiting" -LogLevel 3 402 | Set-Location -Path $StartingLocation 403 | break 404 | } 405 | foreach ($object in $Package) { 406 | write-log -Message "Found $($Object.Name) with ID - $($Object.PackageID)" 407 | } 408 | $PackageDeployments = $Package | foreach-object{Get-CMPackageDeployment -Verbose:$false -PackageID $_.PackageID | Where-Object {$(($_.AdvertFlags -band $BitValue)/$BitValue) -ne '1'}} 409 | if($PackageDeployments -eq $null){ 410 | Write-Log -Message "You have selected a package that does not have any applicable advertisements now exiting..." -LogLevel 3 411 | Set-Location -Path $StartingLocation 412 | break 413 | } 414 | } 415 | if(!($PackageName)){ 416 | 417 | Write-Log -Message "You have selected to return all packages with deployments" -LogLevel 1 418 | $PackageDeployments = Get-CMPackage -Verbose:$False -fast | Where-Object {$_.pkgsourceFlag -eq 2} | foreach-object {Get-CMPackageDeployment -Verbose:$false -PackageID $_.PackageID | Where-Object {$(($_.AdvertFlags -band $BitValue)/$BitValue) -ne '1'}} 419 | if($PackageDeployments -eq $null){ 420 | Write-Log -Message "You have there are no packages that have applicable advertisments now exiting..." -LogLevel 3 421 | Set-Location -Path $StartingLocation 422 | break 423 | } 424 | } 425 | if($PackageDeployments){ 426 | ForEach ($a in $PackageDeployments){ 427 | [int]$currentItem = [array]::indexof($PackageDeployments,$a) 428 | Write-Progress -Activity "Enabling Packages for BranchCache" -Status "Currently Enabling - $($a.ProgramName) - ($($CurrentItem + 1) of $($PackageDeployments.Count)) $([math]::round((($currentItem + 1)/($PackageDeployments.Count + 1)),2) * 100)% " -PercentComplete $([float](($currentItem + 1)/($PackageDeployments.Count + 1)) * 100) 429 | Write-Log -Message "Working on Package $($CurrentItem + 1)/$($PackageDeployments.Count): $($a.PackageID) Program: $($a.ProgramName)" -LogLevel 1 430 | Set-CMPackageDeployment -CollectionID $a.CollectionID -PackageID $a.packageID -StandardProgramName $a.ProgramName -AllowSharedContent $True -Verbose:$false 431 | } 432 | } 433 | else{ 434 | Write-Log -Message "No deployments to process" -LogLevel 2 435 | } 436 | } 437 | 438 | if($Mode -ieq "Disable"){ 439 | Write-Log -Message "Now retriving all of the package deployments in configuration manager" -LogLevel 1 440 | if($PackageName){ 441 | Write-Log -Message "You have selected to only return a specific package" 442 | $Package = Get-CMPackage -Verbose:$False -Name $PackageName -fast 443 | if($Package -eq $null){ 444 | Write-Log -Message "The package was not found now exiting" -LogLevel 3 445 | Set-Location -Path $StartingLocation 446 | break 447 | } 448 | foreach ($object in $Package) { 449 | write-log -Message "Found $($Object.Name) with ID - $($Object.PackageID)" 450 | } 451 | $PackageDeployments = $Package | foreach-object {Get-CMPackageDeployment -Verbose:$false -PackageID $_.PackageID | Where-Object {$(($_.AdvertFlags -band $BitValue)/$BitValue) -ne '0'}} 452 | if($PackageDeployments -eq $null){ 453 | Write-Log -Message "You have selected a package that does not have any applicable advertisements now exiting..." -LogLevel 3 454 | Set-Location -Path $StartingLocation 455 | break 456 | } 457 | } 458 | if(!($PackageName)){ 459 | Write-Log -Message "You have selected to return all packages with deployments" -LogLevel 1 460 | $PackageDeployments = Get-CMPackage -Verbose:$False -fast | Where-Object {$_.pkgsourceFlag -eq 2} | foreach-object {Get-CMPackageDeployment -Verbose:$false -PackageID $_.PackageID | Where-Object {$(($_.AdvertFlags -band $BitValue)/$BitValue) -ne '0'}} 461 | if($PackageDeployments -eq $null){ 462 | Write-Log -Message "You have there are no packages that have applicable advertisments now exiting..." -LogLevel 3 463 | Set-Location -Path $StartingLocation 464 | break 465 | } 466 | } 467 | if($PackageDeployments){ 468 | ForEach ($a in $PackageDeployments){ 469 | [int]$currentItem = [array]::indexof($PackageDeployments,$a) 470 | Write-Progress -Activity "DISABLING Packages for BranchCache" -Status "Currently DISABLING - $($a.ProgramName) - ($($CurrentItem + 1) of $($PackageDeployments.Count)) $([math]::round((($currentItem + 1)/($PackageDeployments.Count + 1)),2) * 100)% " -PercentComplete $([float](($currentItem + 1)/($PackageDeployments.Count + 1)) * 100) 471 | Write-Log -Message "Working on Package $($CurrentItem + 1)/$($PackageDeployments.Count): $($a.PackageID) Program: $($a.ProgramName)" -LogLevel 1 472 | Set-CMPackageDeployment -CollectionID $a.CollectionID -PackageID $a.packageID -StandardProgramName $a.ProgramName -AllowSharedContent $false -Verbose:$false 473 | } 474 | } 475 | else{ 476 | Write-Log -Message "No deployments to process" -LogLevel 2 477 | } 478 | } 479 | Set-Location -Path $StartingLocation 480 | } -------------------------------------------------------------------------------- /MigrationFromLegacyP2P/AdaptivaBranchCache.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Check Free Disk Space, Size of Adaptiva Content etc and import into BranchCache 4 | 5 | .DESCRIPTION 6 | Checks the size of the Adaptiva Content Library and removes unwanted content 7 | Check Free Disk Space 8 | Configures BC & the BranchCache Cache size 9 | Imports content from \AdaptivaCache into BranchCache 10 | Removes Adaptiva Client 11 | Installs SCCM to new site 12 | 13 | 14 | .NOTES 15 | AUTHOR: 2Pint Software 16 | EMAIL: support@2pintsoftware.com 17 | VERSION: 1.0.1.1 18 | DATE:23/04/2020 19 | 20 | CHANGE LOG: 21 | 1.0.0.0 : 02/01/2019 : Initial version of script 22 | 1.0.0.1 : 09/01/2019 : Added Adaptiva 1Site uninstall 23 | 1.0.0.2 : 18/01/2019 : Added extra logging + .NET Framework check 24 | 1.0.0.3 : 23/01/2019 : Inserted a sleep in the CI Function to try to lower CPU hit 25 | 1.0.0.4 : 24/01/2019 : Changed the space calculation and added 'delete as we go' for the content 26 | 1.0.0.5 : 13/02/2019 : Added reg key removal which was preventing SCCM site re-assignment/ change logfile location to c:\work 27 | 1.0.0.6 : 15/02/2019 : Removed StifleR Client install 28 | 1.0.0.7 : 15/02/2019 : Now removes unwanted files first - including SUP over 90 days, and then checks Content IDs agains a whitelist 29 | 1.0.0.8 : 19/03/2019 : Changed the Unzip function - now does unzip and delete (if no errorlevel) 30 | 1.0.0.9 : 20/03/2019 : Fixed a bug with - Content Whitelist import 31 | 1.0.1.0 : 28/03/2019 : Fixed a bug with x86 clients not uninstalling Adaptiva client 32 | 1.0.1.1 : 23/04/2020 : Fast job to cleanup up messy indent from fast work in ISE 33 | .LINK 34 | https://2pintsoftware.com 35 | #> 36 | #======================================= 37 | # SETUP VARIABLES 38 | #======================================= 39 | #delete any existing logfile if it exists 40 | $Logfile = "C:\work\AdaptivaBranchCache.log" 41 | If (Test-Path $Logfile){ri $Logfile -Force -ErrorAction SilentlyContinue -Confirm:$false} 42 | If (!(Test-Path $Logfile)){New-Item -ItemType File -Force -Path $Logfile -ErrorAction SilentlyContinue -Confirm:$false} 43 | if(!$PSScriptRoot){ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent} 44 | 45 | If($env:PROCESSOR_ARCHITECTURE -eq "AMD64") 46 | { 47 | $uninstallexe = "$ENV:ProgramFiles (x86)\Adaptiva\AdaptivaClient\bin\AdaptivaClientSetup.exe" 48 | } 49 | Else 50 | { 51 | $uninstallexe = "$ENV:ProgramFiles\Adaptiva\AdaptivaClient\bin\AdaptivaClientSetup.exe" 52 | } 53 | 54 | 55 | $uninstallargs = "-uninstall" 56 | 57 | #CM Client Package ID 58 | $CMClientPkgID="CP100019"#<<< Set This 59 | $TempFolder="C:\2pstemp" 60 | $CMClientFolder="C:\2pstemp\CMClient" 61 | If (!(Test-Path $CMClientFolder)){New-Item -ItemType Directory -Force -Path $CMClientFolder} 62 | 63 | $ServerSecret="Lh2] [/OutputCSVFile ] [/Quiet] [/Verbose] 159 | $(TimeStamp) + " : Finished Generation of CI file: $CIPath " | Out-File $Logfile -Append 160 | Write-Verbose "$($MyInvocation.MyCommand.Name):: Finished Generation of file: $CIPath" 161 | return $CIPath 162 | } 163 | 164 | End 165 | {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function ended"} 166 | } 167 | 168 | function Add-BCData 169 | { 170 | [CmdletBinding()] 171 | Param( 172 | [ValidateNotNullOrEmpty()] 173 | [ValidateScript({(Test-Path $_) -and ((Get-Item $_).Length -gt 64kb)})] 174 | [Parameter(ValueFromPipeline=$True,Mandatory=$True)] 175 | [string]$FilePath, 176 | [ValidateNotNullOrEmpty()] 177 | [ValidateScript({(Test-Path $_) -and ((Get-Item $_).Length -gt 0)})] 178 | [Parameter(ValueFromPipeline=$True,Mandatory=$True)] 179 | [string]$CIPath 180 | ) 181 | 182 | Begin 183 | {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function started"} 184 | 185 | Process 186 | { 187 | Write-Verbose "$($MyInvocation.MyCommand.Name):: Processing file: $Filepath with CI: $CIPath" 188 | $(TimeStamp) + " : Processing file: $Filepath with CI: $CIPath" | Out-File $Logfile -Append 189 | if(Test-Path -Path $CIPath) 190 | { 191 | $CIContent = Get-Item -Path $CIPath 192 | if($CIContent.Length -gt 0) 193 | { 194 | #We have data in both filess, inject it 195 | $args = @("/AddData", "/InputDataFile", "$FilePath", "/InputCIFile", "$CIPath", "/Quiet") 196 | 197 | Write-Debug "Executing $exe with arguments $args" 198 | &$exe $args 199 | 200 | #[/BufferSizeBytes ] [/OutputCSVFile ] [/Quiet] [/Verbose] 201 | } 202 | else 203 | { 204 | $(TimeStamp) + " : Warning! Missing file data in CI! $CIPath" | Out-File $Logfile -Append 205 | Write-Warning "Missing file data in $CIPath" 206 | } 207 | } 208 | $(TimeStamp) + " : Finished Processing file: $FilePath" | Out-File $Logfile -Append 209 | Write-Verbose "$($MyInvocation.MyCommand.Name):: Finished Processing file: $FilePath" 210 | 211 | } 212 | 213 | End 214 | {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function ended"} 215 | } 216 | 217 | function InjectData 218 | { 219 | [CmdletBinding()] 220 | Param( 221 | [ValidateNotNullOrEmpty()] 222 | [ValidateScript({(Test-Path $_) -and ((Get-Item $_).Length -gt 64kb)})] 223 | [Parameter(ValueFromPipeline=$true,Mandatory=$true)] 224 | [string]$FilePath, 225 | [Parameter()][bool]$DeleteCI = $true, 226 | [Parameter()][bool]$DeleteFile = $true, 227 | [Parameter()][bool]$GenerateV1 = $false 228 | ) 229 | 230 | Begin 231 | {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function started"} 232 | 233 | Process 234 | { 235 | Write-Verbose "$($MyInvocation.MyCommand.Name):: Processing file: $Filepath" 236 | 237 | #Genereate the CI with BranchCacheTool.exe 238 | 239 | $CIPath = $FilePath + ".ci"; 240 | 241 | if(($GenerateV1 -eq $true) -or (($GenerateV1 -eq $False) -and ($V2Capable -eq $False))) 242 | { 243 | $CIVersion = "V1"; 244 | } 245 | else 246 | { 247 | $CIVersion = "V2" 248 | } 249 | $(TimeStamp) + " : CI Version is $CIVersion" | Out-File $Logfile -Append 250 | Write-Debug "CI Version is $CIVersion" 251 | $CIPath = $CIPath + ".$CIVersion" 252 | Write-Debug "Calling function New-CI -FilePath $FilePath -CIPath $CIPath" 253 | 254 | $return = New-CI -FilePath $FilePath -CIPath $CIPath -CIVersion $CIVersion 255 | 256 | Write-Debug "We have a CI path, now we verify the file: $CIPath" 257 | 258 | if(Test-Path -Path $CIPath) 259 | { 260 | $CIContent = Get-Item -Path $CIPath 261 | if($CIContent.Length -gt 0) 262 | { 263 | Write-Debug "We have a CI, now we generate the data from the CI: $CIPath" 264 | Add-BCData -FilePath $FilePath -CIPath $CIPath 265 | $(TimeStamp) + " : Injected $FilePath into the BranchCache Cache, delete the CI:$DeleteCI" | Out-File $Logfile -Append 266 | Write-Output "Injected $FilePath into the BranchCache Cache, delete the CI:$DeleteCI" 267 | Write-Debug "Delete the CI file: $CIPath" 268 | If ($DeleteCI){Remove-Item $CIPath} 269 | Write-Debug "Delete the file: $CIPath" 270 | If ($DeleteFile){Remove-Item $FilePath} 271 | } 272 | else 273 | { 274 | $(TimeStamp) + " : Missing file data in $CIPath" | Out-File $Logfile -Append 275 | Write-Warning "Missing file data in $CIPath" 276 | } 277 | } 278 | else 279 | { 280 | $(TimeStamp) + " : Warning!Missing CI file: $CIPath" | Out-File $Logfile -Append 281 | Write-Warning "Missing CI file: $CIPath" 282 | } 283 | 284 | Write-Verbose "$($MyInvocation.MyCommand.Name):: Finished Processing file: $FilePath" 285 | 286 | } 287 | 288 | End 289 | {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function ended"} 290 | } 291 | #======================================= 292 | # Injector FUNCTIONS - END 293 | #======================================= 294 | 295 | #check OS version - if Win10 we can do V2 content 296 | $V2Capable = $false 297 | $OS = [Environment]::OSVersion 298 | if(($OS.Version.Major -gt 6) -or (($OS.Version.Major -eq 6) -and ($OS.Version.Minor -ge 2))) 299 | { 300 | $V2Capable = $true 301 | } 302 | 303 | 304 | #Check for BranchCacheTool.exe as we need that 305 | $checkEXE = Get-Item -Path $exe -ErrorAction SilentlyContinue 306 | 307 | if(!$checkEXE.Exists) 308 | { 309 | $(TimeStamp) + " : Fail - seems BranchCacheTool.exe is missing" | Out-File $Logfile -Append 310 | Write-Error "Fail - seems BranchCacheTool.exe is missing" 311 | Return 312 | } 313 | 314 | Write-Debug "Made it so far! Path is: $Path"; 315 | 316 | #Check the path to the content is valid 317 | if(!(Test-Path -Path $Path)) 318 | { 319 | $(TimeStamp) + " : Error - Target Folder does not exist!" | Out-File $Logfile -Append 320 | Write-Error "Error - Target Folder does not exist!" 321 | Return 322 | } 323 | else 324 | { 325 | Write-Debug "Content folder exists..." 326 | } 327 | 328 | #Get the files to process (note slightly different cmd depending on OS version) 329 | $directory = Get-Item $Path 330 | If ($V2Capable){$files = $directory | Get-ChildItem -File -Recurse} 331 | Else 332 | #W7 version - Get-ChildItem doesn't support the -file parameter 333 | {$files = $directory | Get-ChildItem -Recurse | Where-Object { !$_.PSIsContainer }} 334 | 335 | #check that there is actual content - if not - quit 336 | if($files.Length -gt 0) 337 | { 338 | Write-Debug " Adding files:" 339 | $(TimeStamp) + " : Adding Files of size: " + $files.Length | Out-File $Logfile -Append 340 | } 341 | else 342 | { 343 | Write-Warning "No data to inject! Exiting" 344 | $(TimeStamp) + " : No data to inject! Exiting" | Out-File $Logfile -Append 345 | Return 346 | } 347 | 348 | 349 | #===================== 350 | #Main lifting of files 351 | #===================== 352 | #If we are on Win10 but $GenerateV1 is set to $true we will gen a V1 hash for each file 353 | #This doubles the disk requirement 354 | Try 355 | { 356 | if(($GenerateV1 -eq $true) -and ($V2Capable -eq $true)) 357 | { 358 | Write-Debug "We are also generating Version 1 content, lets start with that" 359 | foreach ($file in $files){ 360 | If(($file.Length -gt 64kb) -and ($file.Length -le $FreeSpaceOnC)){$File.fullname | InjectData -DeleteFile $true -GenerateV1 $true -DeleteCI $true 361 | Start-Sleep -s 2} # added a sleep here to try to avoid CPU stress 362 | Else {ri $File.fullname -Force -ErrorAction SilentlyContinue -Confirm:$false} 363 | } 364 | #we don't delete the source file in this pass as we might still need it 365 | } 366 | } 367 | Catch { 368 | $(TimeStamp) + "BranchCache Inject Error: " + (Write-Error -Message $_) | Out-File -FilePath $Logfile -Append -Encoding ascii 369 | Return 370 | } 371 | Try 372 | { 373 | #This generates V1 for Win7 and Gen2 for Gen2 Capable ones 374 | Write-Debug "Generating Version 1 or 2 content, depending on OS Ver" 375 | foreach ($file in $files){ 376 | If($file.Length -gt 64kb){$File.fullname | InjectData -DeleteFile $true -DeleteCI $true 377 | Start-Sleep -s 2} # added a sleep here to try to avoid CPU stress 378 | Else {ri $File.fullname -Force -ErrorAction SilentlyContinue -Confirm:$false} 379 | } 380 | } 381 | Catch { 382 | $(TimeStamp) + "BranchCache Inject Error: " + (Write-Error -Message $_) | Out-File -FilePath $Logfile -Append -Encoding ascii 383 | Return 384 | } 385 | 386 | } 387 | 388 | 389 | #======================================= 390 | # END - FUNCTIONS 391 | #======================================= 392 | 393 | 394 | #========================================================================================================================== 395 | # MAIN STARTS HERE 396 | #========================================================================================================================== 397 | #Stop the AdaptivaClient Svc 398 | $(TimeStamp) + " : Stopping the Adaptiva Client Service " | Out-File $Logfile -Append 399 | $s = Get-Service -name 'adaptivaclient' -ErrorAction SilentlyContinue 400 | #Stop AdaptivaClient 401 | If ($s){ 402 | Stop-Service $s.name -Force 403 | } 404 | #if for some reason the service didn't stop correctly - force it. 405 | If (!$s?) {get-process adaptivaclient* | stop-process -Force} 406 | #END Stop AdaptivaClient Svc 407 | 408 | #======================================= 409 | # Enable BranchCache and set the cache size 410 | # 411 | #======================================= 412 | #check OS version - if Win10 we can do V2 content and also use BranchCache PS cmdlets 413 | $V2Capable = $false 414 | $OS = [Environment]::OSVersion 415 | if(($OS.Version.Major -gt 6) -or (($OS.Version.Major -eq 6) -and ($OS.Version.Minor -ge 2))) 416 | { 417 | $V2Capable = $true 418 | } 419 | $SetBCCommand = {netsh branchcache set service mode=distributed} 420 | $SetBCCacheSizeCommand = {netsh branchcache set cachesize $CacheSize} 421 | 422 | $(TimeStamp) + " : Setting BranchCache service to Distributed Mode " | Out-File $Logfile -Append 423 | Invoke-Command -ScriptBlock $SetBCCommand 424 | 425 | $AdaptivaCache="C:\adaptivacache" 426 | $AdaptivaContentSizeInBytes = 0 427 | #Remove the .content files that we don't need to migrate 428 | foreach ($file in (get-childitem $AdaptivaCache\* -Include *OSInstallPkg*,*DriverPkg*,*ImgPkg*, *policy*, *workflow*)) {ri $file -ErrorAction SilentlyContinue -Force} 429 | #Remove the SUP files older than 3 months 430 | $AgeLimit = (Get-Date).AddDays(-90) 431 | foreach ($file in (get-childitem $AdaptivaCache\* -Include *SMSSUP*| Where-Object {$_.LastwriteTime -lt $AgeLimit})) {ri $file -ErrorAction SilentlyContinue -Force} 432 | #Now we can get the total amount of content to migrate 433 | foreach ($file in (get-childitem $AdaptivaCache\* -Include *.content)) {$AdaptivaContentSizeInBytes += $file.length} 434 | 435 | $(TimeStamp) + " : Adaptiva Content to migrate is $AdaptivaContentSizeInBytes Bytes" | Out-File $Logfile -Append 436 | Write-Debug "Adaptiva Cache Content is $AdaptivaContentSizeInBytes Bytes" 437 | # Get the free space on C: from WMI 438 | $SystemDrive = Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='C:'" 439 | $FreeSpaceOnC = $Systemdrive.FreeSpace 440 | 441 | Write-Debug "Free Disk space on C: is $FreeSpaceOnC Bytes" 442 | $SkipImport=$False 443 | If (!(Test-Path $adaptivacache)){$SkipImport=$True 444 | $(TimeStamp) + " : Adaptiva Cache not found - skipping import of content" | Out-File $Logfile -Append} 445 | 446 | #If ($FreeSpaceOnC -lt 10737418240) 447 | #{$(TimeStamp) + " : Less than 10GB on this system - Skipping Content import" | Out-File $Logfile -Append 448 | #Write-warning "Less than 10GB on this system - Skipping import" 449 | #$SkipImport=$True 450 | #set cache size to half of free space 451 | #$CacheSize = $FreeSpaceOnC/2 452 | #} 453 | #If Windows 10 + Plenty of diskspace (2 X AdaptivaCache + 10GB) we can do V1 and 2 content 454 | If ($V2Capable -eq $True){ 455 | If(((($AdaptivaContentSizeInBytes *2) + 10737418240)) -lt $FreeSpaceOnC) 456 | {$CacheSize = $FreeSpaceOnC-10737418240 457 | $GenV1 = $True} 458 | ElseIf (($AdaptivaContentSizeInBytes + 10737418240) -lt $FreeSpaceOnC) 459 | {$CacheSize = $AdaptivaContentSizeInBytes 460 | $GenV1 = $False} 461 | Else {$CacheSize=($FreeSpaceOnC * 0.9) 462 | $GenV1 = $False} 463 | } 464 | Else 465 | { 466 | $CacheSize=($FreeSpaceOnC * 0.9) 467 | $GenV1 = $False 468 | } 469 | 470 | $(TimeStamp) + " : Setting BC Cache to $CacheSize" | Out-File $Logfile -Append 471 | Write-Debug "Setting BC Cache to $CacheSize" 472 | Invoke-Command -ScriptBlock $SetBCCacheSizeCommand 473 | 474 | 475 | #======================================= 476 | # SET The Server Secret to same as the CM DP 477 | # And backup the old one - this will be reset once the migration is complete 478 | #======================================= 479 | $netsh = "$Env:windir\System32\netsh.exe" 480 | if($ServerSecret -ne "") 481 | { 482 | $(TimeStamp) + " : Reading the current BranchCache secret key" | Out-File $Logfile -Append 483 | Write-Debug "Reading the current BranchCache secret key" 484 | $OldKey = Get-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PeerDist\SecurityManager\Restricted" -Name Seed 485 | $PassPhrase = [System.Text.Encoding]::Unicode.GetString($OldKey.Seed) 486 | $(TimeStamp) + " : Changing the Server Secret for the injection to: $ServerSecret" | Out-File $Logfile -Append 487 | Write-Debug "Changing the Server Secret for the injection to: $ServerSecret" 488 | &$netsh @("BranchCache", "set","key","passphrase=$ServerSecret") 489 | } 490 | #======================================= 491 | # End Server Secret Setup 492 | #======================================= 493 | #======================================= 494 | # Output Some BranchCache Stats 495 | #======================================= 496 | 497 | If ($V2Capable -eq $True) { 498 | $BCDataCache = Get-BCDataCache 499 | $CurrentActiveCacheSizeAsMB = [math]::truncate($BCDataCache.CurrentActiveCacheSize/1MB); 500 | $CurrentSizeOnDiskAsMB = [math]::truncate($BCDataCache.CurrentSizeOnDiskAsNumberOfBytes/1MB); 501 | $(TimeStamp) + " : Before Import: We have $CurrentActiveCacheSizeAsMB MB in Cache taking up a total of $CurrentSizeOnDiskAsMB MB on disk." | Out-File $Logfile -Append 502 | Write-Output "Before: We have $CurrentActiveCacheSizeAsMB MB in Cache taking up a total of $CurrentSizeOnDiskAsMB MB on disk." 503 | } 504 | 505 | $TargetFolder = "c:\2pstemp\content" 506 | If (!(Test-Path $TargetFolder)){New-Item -ItemType Directory -Force -Path $TargetFolder} 507 | 508 | Try{ 509 | If ($SkipImport -eq $False) 510 | 511 | { 512 | #load the content Whitelist 513 | $ContentWhiteList = import-csv -Path "$PSScriptRoot\ContentWhitelist.csv" 514 | } 515 | Catch { 516 | $(TimeStamp) + "Error Importing Whitelist: " + (Write-Error -Message $_) | Out-File -FilePath $Logfile -Append -Encoding ascii 517 | Exit 1 518 | } 519 | 520 | 521 | foreach ($file in (get-childitem $AdaptivaCache\* -Include *.content |Sort-Object LastWriteTime -Descending)){ 522 | #Start with a clean target folder 523 | If (Test-Path $TargetFolder){ri $TargetFolder\* -Recurse -Force -ErrorAction SilentlyContinue -Confirm:$false} 524 | $(TimeStamp) + "Processing Content: " + $file.Name | Out-File -FilePath $Logfile -Append 525 | 526 | Switch -Wildcard ($file.Name) 527 | { 528 | #skip the SCCM Client package we don't need it 529 | *$CMClientPkgID*{Write-Debug "Skipping CM Client Package: $file" 530 | ;break 531 | } 532 | 533 | App$* {Write-Debug "Processing App: $file" 534 | #Check the content ID against the whitelist - if not found - just delete it 535 | If($ContentWhiteList.Where({$PSItem.ContentID -eq ($file.Name).Substring(4,8)})){ 536 | 537 | unzip $file.FullName $TargetFolder 538 | If (Test-Path $TargetFolder\AdaptivaSecureHash.xml){ri $TargetFolder\AdaptivaSecureHash.xml -Force -ErrorAction SilentlyContinue -Confirm:$false} 539 | Write-Debug "Launching the BCInjector" 540 | BCInjector -GenerateV1 $GenV1 541 | } 542 | Else{ri $file -ErrorAction SilentlyContinue -Force 543 | Write-Debug "Content is not on the Whitelist - so we nuked it"} 544 | } 545 | 546 | SmsPkg$* { 547 | Write-Debug "Processing Package: $file" 548 | #Check the content ID against the whitelist - if not found - just delete it 549 | If($ContentWhiteList.Where({$PSItem.ContentID -eq ($file.Name).Substring(7,8)})){ 550 | unzip $file.FullName $TargetFolder 551 | If (Test-Path $TargetFolder\AdaptivaSecureHash.xml){ri $TargetFolder\AdaptivaSecureHash.xml -Force -ErrorAction SilentlyContinue -Confirm:$false} 552 | Write-Debug "Launching the BCInjector" 553 | BCInjector -GenerateV1 $GenV1 554 | } 555 | Else{ 556 | ri $file -ErrorAction SilentlyContinue -Force 557 | Write-Debug "Content is not on the Whitelist - so we nuked it"} 558 | } 559 | SmsSup* {Write-Debug "Processing SUP: $file" 560 | unzip $file.FullName $TargetFolder 561 | If (Test-Path $TargetFolder\AdaptivaSecureHash.xml){ri $TargetFolder\AdaptivaSecureHash.xml -Force -ErrorAction SilentlyContinue -Confirm:$false} 562 | Write-Debug "Launching the BCInjector" 563 | BCInjector -GenerateV1 $GenV1 564 | } 565 | 566 | 567 | default { 568 | Write-Debug "Unknown Item! - Skipping..." 569 | $(TimeStamp) + "Unknown File - Skipping: " + $file.Name | Out-File -FilePath $Logfile -Append 570 | } 571 | 572 | } 573 | 574 | } 575 | 576 | } 577 | finally 578 | { 579 | 580 | } 581 | 582 | #skip import 583 | #============================================ 584 | #reset the server secret to the original value 585 | #============================================ 586 | if($ServerSecret -ne "") 587 | { 588 | $(TimeStamp) + " :Resetting the Server Secret after the injection "| Out-File -FilePath $Logfile -Append 589 | Write-Output "Resetting the Server Secret after the injection: $PassPhrase" 590 | &$netsh @("BranchCache", "Set","key","passphrase=$PassPhrase") 591 | } 592 | #============================================ 593 | #Output some BranchCache Statistics 594 | #============================================ 595 | If ($V2Capable -eq $True) { 596 | $BCDataCache = Get-BCDataCache 597 | $CurrentActiveCacheSizeAsMB = [math]::truncate($BCDataCache.CurrentActiveCacheSize/1MB); 598 | $CurrentSizeOnDiskAsMB = [math]::truncate($BCDataCache.CurrentSizeOnDiskAsNumberOfBytes/1MB); 599 | $(TimeStamp) + " :After: We have $CurrentActiveCacheSizeAsMB MB in Cache taking up a total of $CurrentSizeOnDiskAsMB MB on disk. "| Out-File -FilePath $Logfile -Append 600 | Write-Output "After: We have $CurrentActiveCacheSizeAsMB MB in Cache taking up a total of $CurrentSizeOnDiskAsMB MB on disk." 601 | Write-Warning "Please note that some of these figures might be inaccurate as data is flushed in and out of the cache, also there is a delay factor." 602 | } 603 | #========================= 604 | #Remove the Adaptiva Client 605 | #========================= 606 | Try 607 | { 608 | $(TimeStamp) + " :Removing the Adaptiva Client "| Out-File -FilePath $Logfile -Append 609 | If (Test-Path $uninstallexe) 610 | { 611 | start-process $uninstallexe -arg $uninstallargs -Wait 612 | } 613 | } 614 | Catch 615 | { 616 | $(TimeStamp) + " :The Adaptiva Client is not installed or there was an error "| Out-File -FilePath $Logfile -Append 617 | Exit 1 618 | } 619 | 620 | #========================= 621 | #END Remove the Adaptiva Client 622 | #========================= 623 | 624 | #Cleanup the temp folders 625 | If (Test-Path $TempFolder){Remove-Item $TempFolder -Recurse -Force -ErrorAction SilentlyContinue -Confirm:$false} 626 | 627 | ri $AdaptivaCache -Recurse -ErrorAction SilentlyContinue -Force 628 | --------------------------------------------------------------------------------