Tuesday, 14 August 2012

PowerShell - Invoke SCCM DCM rule evaluation remotely.

Here at the company I contract too we use SCCM DCM to maintain a base list of hotfixes on our SCCM site servers. Whilst installing a new set of additional hotfixes I needed a quick and easy way to remotely update the DCM compliance state for any given server. Below is a PowerShell module I found and modified to perform this task easily. I have also gone one step further and added it into an SCCM.psm1 module which I use a profile script to load whenever I open the PS shell so the command is always available for use.

Note: The Return code 0 indicated the DCM baseline evaluation has been triggered successfully.



function Invoke-SCCMDCMEvaluation
{
    param (
        [Parameter(Mandatory=$true, HelpMessage="Computer Name",ValueFromPipeline=$true)] $ComputerName
           )
    $Baselines = Get-WmiObject -ComputerName $ComputerName -Namespace root\ccm\dcm -Class SMS_DesiredConfiguration
    $Baselines | % { ([wmiclass]"\\$ComputerName\root\ccm\dcm:SMS_DesiredConfiguration").TriggerEvaluation($_.Name, $_.Version) }
}

Invoke-SccmBaselineEvaluation localhost

PowerShell - Get SCCM DCM compliance remotely.

Here at the company I contract too we use SCCM DCM to maintain a base list of hotfixes on our SCCM site servers. Whilst installing a new set of additional hotfixes I needed a quick and easy way to remotely view the current DCM compliance state for any given server. Below is a PowerShell module I wrote to return this information easily. I have also gone one step further and added it into an SCCM.psm1 module which I use a profile script to load whenever I open the PS shell so the command is always available for use.


Version 1 Simple Function

function Get-SCCMDCMInfo
{
    param (
        [Parameter(Mandatory=$true, HelpMessage="Computer Name",ValueFromPipeline=$true)] $ComputerName
    )
    $Baselines = Get-WmiObject -ComputerName $ComputerName -Namespace root\ccm\dcm -Class SMS_DesiredConfiguration
    $Baselines | Select @{Name="ComputerName";Expression={$_.__Server}}, @{Name="Baseline Display Name";Expression={$_.Displayname}}, Version, @{Name="Eval Status";Expression={$_.Status}},@{Name="Compliance Status";Expression={$_.Lastcompliancestatus}}, @{Name="Last Evaluation Time";Expression={$_.ConvertToDateTime($_.LastEvalTime)}} | format-table -AutoSize
}

Get-SCCMDCMInfo localhost

Version 2 Advanced Function

I have now spent a little time adding to the basic function I created earlier. Now it will perform a ping check, handle piping and display the DCM baseline GUID when there is no display name value.



function Get-SCCMDCMInfo {

<#
.SYNOPSIS
Returns the current evaluation state and compliance state for a target workstation. 
        If a new baseline has been recieved by a client but not yet been evaluated then
        the 'Baseline Display Name' will return the baseline GUID.  

.PARAMETER  Computer
The computer name to target.

.EXAMPLE
PS C:\> Get-SCCMDCMInfo -ComputerName "Foo"

    .EXAMPLE
PS C:\> Get-SCCMDCMInfo Foo

    .EXAMPLE
PS C:\> Get-SCCMDCMInfo Foo Foo2

#>
[CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, HelpMessage="Computer Name",ValueFromPipeline=$true)] $ComputerName
    )

    Process {

    foreach ($computer in $ComputerName) {

        if(Test-Connection -Cn $computer -BufferSize 16 -Count 1 -ea 0 -quiet){
     
            try
   {
                $ComplainceHash = [hashtable]@{
                                "0" = 'Non-Compliant'
                                "1" = 'Compliant'
                                "2" = 'Submitted'
                                "3" = 'Detecting'
                                "4" = 'Detecting'
                                "5" = 'Not Evaluated'                  
                        }   
                $EvalHash = [hashtable]@{
                                "0" = 'EVALUATING'
                                "1" = 'Evaluated'
                                "5" = 'Not Evaluated'                                   
                        } 


                $Baselines = Get-WmiObject -ComputerName $Computer -Namespace root\ccm\dcm -Class SMS_DesiredConfiguration
                $Baselines | Select @{Name="ComputerName";Expression={$_.__Server}},
                                    @{Name="Baseline Display Name";Expression={if ($_.Displayname) {$_.DisplayName} else {$_.Name.split("/")[1]}}},
                                    Version,
                                    @{Name="Eval Status";Expression={$EvalHash[$_.Status.tostring()]}},
                                    @{Name="Compliance Status";Expression={$ComplainceHash[$_.Lastcompliancestatus.tostring()]}},
                                    @{Name="Last Evaluation Time";Expression={$_.ConvertToDateTime($_.LastEvalTime)}} | format-table -AutoSize
               }

            catch
        {
      throw
        }
               
      
     }
        else
                {
                     Write-Host "" 
                     Write-Host $($Computer).ToUpper()"is NOT Online." -ForegroundColor DarkYellow
                     Write-Host ""
                }
        }
    }
}
 
Version 3 Advanced Function that outputs a usable object.
 
I have realized the error of my ways and no longer use Write-Host to view results, instead the module is now out putting an actual object that can for example be easily piped to Out-GridView. I have also included better error handling and errors now also get piped out as objects for much better usability.
 
function Get-SCCMDCMInfo {
<#
 .SYNOPSIS
  Returns the current evaluation state and compliance state for a target workstation.
        If a new baseline has been recieved by a client but not yet been evaluated then
        the 'Baseline Display Name' will return the baseline GUID. 
 .PARAMETER  Computer
  The computer name to target.
 .EXAMPLE
  PS C:\> Get-SCCMDCMInfo -ComputerName "Foo"
    .EXAMPLE
  PS C:\> Get-SCCMDCMInfo Foo
    .EXAMPLE
  PS C:\> Get-SCCMDCMInfo Foo Foo2
#>
[CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, HelpMessage="Computer Name",ValueFromPipeline=$true)] $ComputerName
    )
    Process {
    $ErrorActionPreference = "SilentlyContinue"
    foreach ($computer in $ComputerName) {  
        if(Test-Connection -Cn $computer -BufferSize 16 -Count 1 -ea 0 -quiet){
    
        $ComplainceHash = [hashtable]@{
                                "0" = 'Non-Compliant'
                                "1" = 'Compliant'
                                "2" = 'Submitted'
                                "3" = 'Detecting'
                                "4" = 'Detecting'
                                "5" = 'Not Evaluated'                 
                        }  
        $EvalHash = [hashtable]@{
                                "0" = 'EVALUATING'
                                "1" = 'Evaluated'
                                "5" = 'Not Evaluated'                                  
                        }
            try
          { 
                $Baselines = Get-WmiObject -ComputerName $Computer -Namespace root\ccm\dcm -Class SMS_DesiredConfiguration -ErrorVariable myerror -ErrorAction Stop
                Write-Debug "Performing WMI query"
                }

            catch
           {
         $object = New-Object –TypeName PSObject
                        $object | Add-Member –MemberType NoteProperty –Name 'Computer Name' -value $($Computer).ToUpper()
                        $object | Add-Member –MemberType NoteProperty –Name 'Baseline Display Name' -value $MyError.ErrorRecord
                        $object | Add-Member –MemberType NoteProperty –Name 'Eval Status' -Value "Error"
                        $object | Add-Member –MemberType NoteProperty –Name 'Compliance Status' -Value "Error"
                        
                        Write-Output $object
                        Write-Debug "Catching the error."
           }
            Foreach ($Baseline in $Baselines){
                $object = New-Object –TypeName PSObject
                    $object | Add-Member –MemberType NoteProperty –Name 'Computer Name' -value $($Computer).ToUpper()
                    $object | Add-Member –MemberType NoteProperty –Name 'Baseline Display Name' -value $(if ($Baseline.Displayname) {$Baseline.DisplayName} else {$Baseline.Name.split("/")[1]})
                    $object | Add-Member –MemberType NoteProperty –Name 'Version' -Value $Baseline.version
                    $object | Add-Member –MemberType NoteProperty –Name 'Eval Status' -Value $EvalHash[$Baseline.Status.tostring()]
                    $object | Add-Member –MemberType NoteProperty –Name 'Compliance Status' -Value $ComplainceHash[$Baseline.Lastcompliancestatus.tostring()]
                    $object | Add-Member –MemberType NoteProperty –Name 'Last Evaluation Time' -Value $Baseline.ConvertToDateTime($Baseline.LastEvalTime)
                   
                    Write-Output $object
                    Write-Debug "Writting whole objects to the pipeline"
                    }
     
     }
        else
                {
                     $object = New-Object –TypeName PSObject
                        $object | Add-Member –MemberType NoteProperty –Name 'Computer Name' -value $($Computer).ToUpper()
                        $object | Add-Member –MemberType NoteProperty –Name 'Eval Status' -Value "Offline"
                        $object | Add-Member –MemberType NoteProperty –Name 'Compliance Status' -Value "Offline"
                        Write-Output $object
                        Write-Debug "Writting an object just to state the machine is offline."
                }
        }
    }
}
 


Monday, 6 August 2012

SCCM - Delete orphaned\locked package distribution.

Today I encountered an issue where a DP assignment was locked in the SCCM console at the package creation level and missing altogether at the child primary site. From this I assume it was previously distributed at the child site however when it was later undistributed the status message did not make it to the central site to update its record of the DP assignment change. The following SQL query will delete the orphaned\locked record on the current site. Be sure to ONLY run this query on orphaned DP assignment records, any deletions you perform with this method will NOT replicate to child sites or actually trigger a package removal from a DP.

--Displays current table records for DP assignment for a specific package on a specific DP.
Select * FROM PkgServers WHERE PkgID = 'ABC00123' AND NALPath Like '%DP_SERVER01%'

--Deletes the DP assignment on the current site only, this will not replicate to child site. Make sure this is only --done for orphaned records.
--DELETE FROM PkgServers WHERE PkgID = 'ABC00123' AND NALPath Like '%DP_SERVER01%'

Note: Modifying the SCCM site database is totally unsupported by Microsoft and you are taking your life in your own hands by doing so. This information is for reference only and should not be used without direct consultation with Microsoft Premier Support Services.

Thursday, 2 August 2012

PowerShell - Get SCCM related windows services.


Below is a quick single Powershell line I have created to display windows services that relate to the operations of SCCM. This command allows for a very quick remote view of the service running state of an SCCM site server.

Get-Service -computername $ServerName -include *SMS_*,ccmexec,w3svc,wdsserver,wsusservice,SQLServeragent

I have actually added the command line as a function into a .psm1 module file on our management server and  then published the Powershell console through RDS so others can benefit from this tool among others I have created. I would highly recommend others consider adding any Powershell 'tools' they create into a module on a terminal server and then publish the shell for others to use. I have found this to be a particularly useful approach in a large organisation.


File Path:
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\SCCM\SCCM.psm1

Function Get-SCCMServices {
    [CmdletBinding()]
    PARAM
    (
        [Parameter(Mandatory=$true, HelpMessage="SCCM Site Server",ValueFromPipeline=$true)] $ServerName
    ) 
            Get-Service -computername $ServerName -include *SMS_*,ccmexec,w3svc,wdsserver,wsusservice,SQLServeragent

 }


How to troubleshoot a stalled Branch Distribution Point.

I have found the following monitoring and recovery steps very useful when troubleshooting SCCM branch distribution points that are downloading newly assigned packages.


Monitoring Steps:
1\ On the BDP workstation or server:
        PeerDPAgent.log
       ContentTransferManager.log
       Monitor BITs client traffic - 'bitsadmin.exe /monitor /allusers /refresh 1'
2\ On Parent site server:
      Open native IIS logs with trace32.exe and filter for the IP address of the BDP to view BDP only traffic.

Recovery Steps on the BDP:
1\ Stop the SMS Agent Host Service
2\ Take ownership of the 'BDPTmpWrkFldr and delete.
3\ Delete the BDP temporary working folder 'BDPTmpWrkFldr'.
4\ Start the SMS Agent Host Service
5\ Trigger the 'Branch Distribution Point Maintenance Task' agent action.

Fig1. The tempoary working folder for the BDP.

Fig2. The SCCM agent action related to the branch distribution point.

Fig3. Monitoring BITs traffic on the server hosting the BDP role.