How To – Find All DPS missing NO_SMS_ON_DRIVE on C:\ – And Fix it

C:\ Drive on my DP ran out of space… again…

Something that constantly comes up is forgetting to place the NO_SMS_ON_DRIVE.SMS file on new distribution point servers. While this isn’t the end of the world its certainly frustrating when the C:\ drive runs out of space. Just in case you don’t know what I’m talking about, the NO_SMS_ON_DRIVE.SMS file is a feature of Configuration Manager that has been around for quite a while. This file is what informs the distribution point role to not create a content library on a specific drive.

Gathering DP Information

The first thing we need to do is get our distribution point information. We can do this one of two ways. We can either get all of the distribution points using ConfigMgr cmdlets, or we can use raw WMI queries. I will showcase both methods.

First we need to gather our distribution points and the drives attached to them. We can do this via WMI or the ConfigMgr cmdlets but WMI is more efficient. Let’s start simple with the below code. This code will use the distinct ServerName property for all of your distribution points and return one copy of each DP.

$SiteCode = "YourSiteCode
$ConfigMgrServer = "ConfigMgr Server"
$DPList = Get-WmiObject -ComputerName $ConfigMgrServer -Namespace root\sms\site_$SiteCode -Query "select distinct ServerName from sms_distributionpointInfo"
$DPList

Now we have all our DPS , however if you’ve worked with this WMI section before you know that if we didn’t filter, we would get entries for EVERY drive on each of those DPS without the SMS on Drive Filter. Thats something we don’t want yet. Now all we need to do is iterate through the list of DP’s and check to see if C:\ has the NO_SMS_ON_Drive.SMS file present.

ForEach($DP in $DPList){
   $Result = Test-Path -Path "filesystem::\\$($DP.ServerName)\C$\NO_SMS_ON_DRIVE.SMS"
   Write-Verbose -Message "The Server $($DP.ServerName) returned $($Result) for having the NO_SMS_ON_DRIVE.SMS" -Verbose
}

This will return what servers have and do not have the SMS on drive file.

We found the servers missing it can we fix it?

This leads us to, but can we FIX it remotely with PowerShell? Well sure we can lets start by collecting our information a little more “elegantly”

$SiteCode = "PR1"
$ConfigMgrServer = "PROBRESCM01"
#The dynamic variable works if the admin console is installed in the same directory however I've seen instances of it being installed in X86 and configmgr is in Program Files. 
#$SerVerToolsPath = $(split-path(Split-Path(split-path $env:SMS_ADMIN_UI_PATH)))\tools\ServerTools
$SerVerToolsPath = "C:\Program Files\Microsoft Configuration Manager\tools\ServerTools"



$DPList = Get-WmiObject -ComputerName $ConfigMgrServer -Namespace root\sms\site_$SiteCode -Query "select distinct ServerName from sms_distributionpointInfo"
$Status = New-Object System.Collections.ArrayList
ForEach($DP in $DPList){
   $Result = Test-Path -Path "filesystem::\\$($DP.ServerName)\C$\NO_SMS_ON_DRIVE.SMS"
   Write-Verbose -Message "The Server $($DP.ServerName) returned $($Result) for having the NO_SMS_ON_DRIVE.SMS" -Verbose
   $hash = [ordered]@{
        SERVERNAME = $DP.ServerName
        RESULT = $Result
   }
   $CurrentObject = New-Object -TypeName PSObject -Property $hash
   $Status.Add($CurrentObject) | Out-Null
}

This collects all of our information and builds it into an array list object. Now the first step to migrating content is to copy the needed tools to the destination server. That’s easy enough.

$Status | Where-Object {$_.Result -eq $false} | ForEach-Object {Copy-item -Container $SerVerToolsPath -Recurse -Destination "filesystem::\\$($_.ServerName)\C$\"}

#Note that in the above two scripts I have provided the location for the ServerTools folder and used it as a variable. OK Now we just need to MOVE the content remotely on all of those servers.

Considerations before the move

Some IMPORTANT things to consider before this next part of the script.

  • Make sure the distribution points in question are in Maint Mode
  • Make sure no active distribution of content is occurring for those DP’s
  • This script is focused on getting content OFF of C:\ if you need to address this for multiple drives you WILL need to edit this script.

Now with those little disclaimers and info blocks out there how do we remotely invoke the command to start the transfer?

$Status | Where-Object {$_.Result -eq $false} | ForEach-Object {Invoke-Command -ComputerName $ConfigMgrServer -ScriptBlock {C:\ServerTools\contentlibrarytransfer.exe -SourceDrive E -TargetDrive C} -AsJob}

This example line is assuming your DP content is being moved from a drive C to drive E. Obviously you can change this by just changing the drive letters above. So now our jobs are running but we probably want to know when they are done right?

Do{
    $JobStates = $Status | Where-Object {$_.Result -eq $false} | ForEach-Object {Get-Job | Where-Object {$_.Location -match $_.ServerName}}
    Write-Progress -Activity "Content Migration" -CurrentOperation "$(($JobStates.State | Where-Object {$_ -eq "Completed"}).Count) / $(($JobStates.State).Count) jobs have completed"   -PercentComplete $($(($JobStates.State).Count)/(($JobStates.State | Where-Object {$_ -eq "Completed"}).Count + 1) * 100)
    Start-Sleep -Seconds 60
} 
Until(($JobStates.State | Where-Object {$_ -ne "Completed"}).Count -eq 0)
Write-Verbose -Message "Content migration has finished now returning the results from each job. The result SHOULD be 'Content Library Transfer is now complete' if NOT please review the log generated for that servers session" -Verbose
ForEach($Job in $JobStates){
    $Content = Receive-Job -id $Job.Id
    if($Content[$($Content.Length - 3)] -match "Content Library Transfer is now complete !!" ){
        Write-Verbose -Message "$($Job.Location) returned a positive result" -Verbose
    }
    else{
        Write-Warning -Message "$($Job.Location) returned a NEGATIVE result inspect this job" -WarningAction Continue
    }
    Out-File -FilePath $PSScriptRoot\$($Job.Location).LOG -InputObject $Content
}

So we track the jobs! We get the state of all the powerShell jobs and we check to see if all the jobs we just created are done yet. Now, there are some issues with the way this loop ends – it’s assume we only get “completed” or “running” – so far in testing I’ve never seen it “fail” as failure of the content migration returns a completed status. Of course it’s nice to know that it’s working so a progress bar is included.

This leads to other challenges of course. To address that, I’ve added a section to return the content of the job and check the third line from the bottom to see if it has the proper completed message. If it does life is grand and if it’s not, then it spits out the logs. While you could just run this script as you see it written above I recently had to do this for 19 distribution points which had the content scattered all over… I decided that if possible I would rather not do that again and so I gave the script some parameters. Below is an example of the script migrating content from one drive to the other in my lab.

Parameters

.PARAMETER SITECODE This parameter is for entering the site code of your Configuration Manager server. This allows you to run the script from a device that is not your primary.


.PARAMETER ConfigMgrServer This parameter is for entering the site server that you want to get all the distribution points for and correct.


.PARAMETER ServerToolsPath This parameter is for entering the location of the ServerTools directory. It is copied to the SOURCE drive where you are moving your content library AWAY from


.PARAMETER SourceDrive This paremeter specified the source drive letter. DO NOT use a colon symbol or it will fail.


.PARAMETER DestinationDrive This paremeter specified the destination drive letter. DO NOT use a colon symbol or it will fail.


.PARAMETER MigrateContent This parameter is a switch that in enables the migration aspect of the script. Without this switch the content will not be migrated just reported on if the NO_SMS_ON_DRIVE is missing.

With all of this being said we can now output a finalized version of the script. Which you can download here:

https://github.com/JordanTheITGuy/PowerShell/tree/master/BlogPosts/HowTo%20-%20Find%20All%20DPS%20Missing%20NO_SMS_ON_DRIVE

<#
.SYNOPSIS
    This script is used to gather information about distribution points and if the C:\ drive has the NO_SMS_ON_DRIVE.SMS file present. 
    For Servers that do NOT have the file present there is an option to copy the server tools and migrate the content library

.DESCRIPTION
    This script is desinged if you are an organization with a large number of Distribution Points and find that those distribution points were 
    originally not properly configured. One of the key lacks of configuration is that the NO_SMS_ON_DRIVE.SMS file was missing from the C:\ drive.
    The script also has a parameter that if engaged will allow the the content library transfer tool to migrate the content away from the offending drive
    that is missing the blocker file to the drive of your choosing.

.LINK
    https://github.com/JordanTheITGuy/PowerShell/tree/master/BlogPosts/HowTo%20-%20Find%20All%20DPS%20Missing%20NO_SMS_ON_DRIVE

.NOTES
          FileName: Invoke-ContentMigration.PS1
          Author: Jordan Benzing
          Contact: @JordanTheItGuy
          Created: 2019-08-15
          Modified: 2019-08-15

          Version - 0.0.1 - (2019-08-15)

          Shortcut for the Default Server Tools Path $(split-path(Split-Path(split-path $env:SMS_ADMIN_UI_PATH)))\tools\ServerTools


.EXAMPLE
    Example of code run to just check all distribution points that are missing the NO_SMS_ON_DRIVE on C:\
    .\Invoke-ContentMigration.PS1 -SiteCode PR1 -ConfigMgrServer PROBRESCM01 -SourceDrive C
    
.EXAMPLE 
    EXAMPLE of code run to check for all distribution points that are missing NO_SMS_ON_DRIVE on C:\ and if missing it migrate it to the E drive.
    .\Invoke-ContentMigration.PS1 -SiteCode PR1 -ConfigMgrServer PROBRESCM01 -SourceDrive C -MigrateContent -ServerToolsPath "\\Probrescm01\c$\Program Files\Microsoft Configuration Manager\tools\ServerTools" -DestinationDrive E

.PARAMETER SITECODE
    This parameter is for entering the site code of your Configuration Manager server. This allows you to run the script from a device that is not your primary.

.PARAMETER ConfigMgrServer
    This parameter is for entering the site server that you want to get all the distribution points for and correct. 

.PARAMETER ServerToolsPath 
    This parameter is for entering the location of the ServerTools directory. It is copied to the SOURCE drive where you are moving your content library AWAY from

.PARAMETER SourceDrive
    This paremeter specified the source drive letter. DO NOT use a colon symbol or it will fail. 

.PARAMETER DestinationDrive
    This paremeter specified the destination drive letter. DO NOT use a colon symbol or it will fail. 

.PARAMETER MigrateContent
    This parameter is a switch that in enables the migration aspect of the script. Without this switch the content will not be migrated just reported on if the NO_SMS_ON_DRIVE is missing. 
#>

[cmdletbinding(DefaultParameterSetName="None")]
param(
    [Parameter(HelpMessage = "Enter your Site Code",Mandatory=$true)]
    [string]$SiteCode,
    [Parameter(HelpMessage = "Enter your Server Name",Mandatory=$true)]
    [string]$ConfigMgrServer,
    [Parameter(HelpMessage = "Path to the Server Tools folder if not specified we assume it's in the default location",ParameterSetName = "MigrationRequired",Mandatory=$true)]
    [string]$ServerToolsPath,
    [Parameter(HelpMessage = "Source drive that should be used to check for NOSMS on Drive.",Mandatory=$true)]
    [string]$SourceDrive = "C",
    [Parameter(HelpMessage ="Destination Drive - DO NOT include a COLON",ParameterSetName = "MigrationRequired",Mandatory=$true )]
    [string]$DestinationDrive,
    [Parameter(HelpMessage ="Switch that is used to migrate the content if desired",ParameterSetName = "MigrationRequired",Mandatory=$true )]
    [switch]$MigrateContent
)
begin{
    if($ServerToolsPath -and !(Test-Path -Path $ServerToolsPath)){
        Write-Error -Message "We couldn't find the Server tools path that was provided. Exiting..."
        break
    }
    if(!(Test-NetConnection -ComputerName $ConfigMgrServer -Port 445)){
        Write-Error -Message "Cannot connect on the SMB port 445 for WMI accesss and remote PS" -ErrorAction Stop
        break
    }
}

process{
    $DPList = Get-WmiObject -ComputerName $ConfigMgrServer -Namespace root\sms\site_$SiteCode -Query "select distinct ServerName from sms_distributionpointInfo"
    $Status = New-Object System.Collections.ArrayList
    ForEach($DP in $DPList){
    $Result = Test-Path -Path "filesystem::\\$($DP.ServerName)\$($SourceDrive)$\NO_SMS_ON_DRIVE.SMS"
    if($Result -eq $false){
        $DriveAvailable = Test-Path -Path "filesystem::\\$($DP.ServerName)\$($SourceDrive)"
        if($DriveAvailable -eq $false){
            $Result = "NO SUCH DESTINATION"
        }
    }
    Write-Verbose -Message "The Server $($DP.ServerName) returned $($Result) for having the NO_SMS_ON_DRIVE.SMS" -Verbose
    $hash = [ordered]@{
            SERVERNAME = $DP.ServerName
            RESULT = $Result
    }
    $CurrentObject = New-Object -TypeName PSObject -Property $hash
    $Status.Add($CurrentObject) | Out-Null
    }
    $Status
    if($MigrateContent){
        $Status | Where-Object {$_.Result -eq $false} | ForEach-Object {if(!(test-path -path "filesystem::\\$($_.ServerName)\$($SourceDrive)$\ServerTools")){Copy-item -Container $SerVerToolsPath -Recurse -Destination "filesystem::\\$($_.ServerName)\$($SourceDrive)$\"}}
        #NOTE YOU MUST SET THE DRIVE LETTER YOU WANT TO MOVE THE CONTENT TO DOWN HERE
        $Status | Where-Object {$_.Result -eq $false} | ForEach-Object {Invoke-Command -ComputerName $ConfigMgrServer -ScriptBlock {C:\ServerTools\contentlibrarytransfer.exe -SourceDrive $using:SourceDrive -TargetDrive $using:DestinationDrive} -AsJob}
        Do{
            $JobStates = $Status | Where-Object {$_.Result -eq $false} | ForEach-Object {Get-Job | Where-Object {$_.Location -match $_.ServerName}}
            Write-Progress -Activity "Content Migration" -CurrentOperation "$(($JobStates.State | Where-Object {$_ -eq "Completed"}).Count) / $(($JobStates.State).Count) jobs have completed"   -PercentComplete $($(($JobStates.State).Count)/(($JobStates.State | Where-Object {$_ -eq "Completed"}).Count + 1) * 100)
            Start-Sleep -Seconds 60
        } 
        Until(($JobStates.State | Where-Object {$_ -ne "Completed"}).Count -eq 0)
        Write-Verbose -Message "Content migration has finished now returning the results from each job. The result SHOULD be 'Content Library Transfer is now complete' if NOT please review the log generated for that servers session" -Verbose
        ForEach($Job in $JobStates){
            $Content = Receive-Job -id $Job.Id
            if($Content[$($Content.Length - 3)] -match "Content Library Transfer is now complete !!" ){
                Write-Verbose -Message "$($Job.Location) returned a positive result" -Verbose
            }
            else{
                Write-Warning -Message "$($Job.Location) returned a NEGATIVE result inspect this job" -WarningAction Continue
            }
            Out-File -FilePath $PSScriptRoot\$($Job.Location).LOG -InputObject $Content
            Remove-Job -Id $Job.Id
        }
    }
}

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: