Wednesday, 5 December 2012

PowerShell Module - Test SCCM Management Point

I regularly find myself having to remotely start the SMS Agent Host service on site servers and found it annoying to have to open an internet browser just to test the SCCM management point URL's. So in an effort to save time I wrote the following PowerShell module to enable me to easily remotely check the availability of the management point for one or many servers from the command line. The example screen capture below displays the default set of results however if you pipe to 'Select *' then you can additionally see the same page content that would have been displayed in the Internet Explorer, such as the certificate value and child site listing.


PowerShell Module Code


function Get-SCCMMPTest {
    <#
   .SYNOPSIS
        Tests the MPList and MPCert SCCM management point URLs
   
        URLs being tested.
        http://localhost/sms_mp/.sms_aut?mpCert
        http://localhost/sms_mp/.sms_aut?mplist

   .PARAMETER  ComputerName
   The computer name to target.

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

        .EXAMPLE
   PS C:\> Get-SCCMMPTest Foo

        .EXAMPLE
   PS C:\> Get-SCCMMPTest 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){

            $Computer = $Computer.toupper()
            $urlsToTest = @{}
            $urlsToTest["MPCert"] = "http://$Computer/sms_mp/.sms_aut?mpcert"
            $urlsToTest["MPList"] = "http://$Computer/sms_mp/.sms_aut?mplist"

            $userAgent = "PowerShell User"
            $webClient = new-object System.Net.WebClient
            $webClient.Headers.Add("user-agent", $userAgent)

            Foreach ($key in $urlsToTest.Keys) {
         
               $output = $null
               $startTime = get-date
         
               Try{              
                    $output = $webClient.DownloadString($urlsToTest[$key])                          
                     }
         
               Catch {
               
                     }
         
               $endTime = get-date

               Try{
                    $SCCMservice = Get-Service -ComputerName $Computer ccmexec
                    }
                Catch{
           
                    }

               if ($output) {
                  $Results = New-Object System.Object
                  $Results | Add-Member -type NoteProperty -name "Computer Name" -value $Computer
                  $Results | Add-Member -type NoteProperty -name "SMS Agent Host Service" -value $SCCMservice.Status
                  $Results | Add-Member -type NoteProperty -name "URL Tested" -value $key
                  $Results | Add-Member -type NoteProperty -name "State" -value "Success"
                  $Results | Add-Member -type NoteProperty -name "Start Time" -value $startTime.DateTime
                  $Results | Add-Member -type NoteProperty -name "Response Time (Sec)" -value ($endTime - $startTime).TotalSeconds  
                  $Results | Add-Member -type NoteProperty -name "Content" -value $Output                    
                  [String[]]$properties = 'Computer Name','SMS Agent Host Service','URL Tested','State','Start Time','Response Time (Sec)'          
                  [System.Management.Automation.PSMemberInfo[]]$PSStandardMembers = New-Object System.Management.Automation.PSPropertySet DefaultDisplayPropertySet,$properties          
                  $Results | Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $PSStandardMembers                                            
                  $Results    
                            }          
               else {
                  $Results = New-Object System.Object
                  $Results | Add-Member -type NoteProperty -name "Computer Name" -value $Computer
                  $Results | Add-Member -type NoteProperty -name "SMS Agent Host Service" -value $SCCMservice.Status
                  $Results | Add-Member -type NoteProperty -name "URL Tested" -value $key
                  $Results | Add-Member -type NoteProperty -name "State" -value "Fail"
                  $Results | Add-Member -type NoteProperty -name "Start Time" -value $startTime.DateTime
                  $Results | Add-Member -type NoteProperty -name "Response Time (Sec)" -value ($endTime - $startTime).TotalSeconds                    
                  $Results
                            }
                     }
                }
        else
            {
                  $Results = New-Object System.Object
                  $Results | Add-Member -type NoteProperty -name "Computer Name" -value $Computer
                  $Results | Add-Member -type NoteProperty -name "SMS Agent Host Service" -value "Offline"
                  $Results | Add-Member -type NoteProperty -name "URL Tested" -value "Offline"
                  $Results | Add-Member -type NoteProperty -name "State" -value "Fail"
                  $Results | Add-Member -type NoteProperty -name "Start Time" -value "Offline"
                  $Results | Add-Member -type NoteProperty -name "Response Time (Sec)" -value ($endTime - $startTime).TotalSeconds
                  $Results
            }
        }
    }
}

Wednesday, 14 November 2012

PowerShell - Refresh content on all DP's assigned for a package.

Whilst debugging a large SCCM software updates maintenance script I needed to determine how to refresh the content on all assigned distribution points after an update had been removed from a package. The below code will trigger a distribution point refresh on all assigned DP's immediately. When I get time I will look to turn this command in to a module for easier reuse and piping.

$(Get-WmiObject -ComputerName SCCMServer -Namespace root\sms\site_abc -query "Select * from SMS_SoftwareUpdatesPackage where PackageID = 'xxx00088'").RefreshPkgSource()


Thursday, 8 November 2012

PowerShell - Get-SCCMSoftwareUpdateCompliance 2007 Module


Within my role I am forever having to troubleshoot software updates compliance issues on remote hosts and so needed an easier means to getting a quick summary of the remote workstation or server state. I cannot take credit for all the code and if I later find the source I will add credit for them. The original code was in script format so I turned it into an advanced function\module and then added more of my own secret sauce to display further useful information about the machine and missing non-compliant updates to help with diagnosing simple issues. 

I have become a big advocate of advanced functions and modules, especially when custom modules are saved in the location below to enable use by anyone logged on locally and when paired with PowerShell V3  module auto loading life gets much much easier.  

C:\Windows\System32\WindowsPowerShell\v1.0\Modules\SCCM



function Get-SCCMSoftwareUpdateCompliance {

<#
.SYNOPSIS
Checks a ConfigMgr client for required Software Update compliance.

.DESCRIPTION
Checks a ConfigMgr client for required Software Update compliance, if it is found non-compliant for any assigned security updates the list of updates is returned.

.PARAMETER  Computer
The server name to target.

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

#>

[CmdletBinding()]
param(
[Parameter(Position=1, Mandatory=$true, HelpMessage="Computer Name", ValueFromPipelineByPropertyName=$True, ValueFromPipeline=$true)]
        [STRING[]] $ComputerName
)

process{

    foreach ($computer in $ComputerName) {

        if(Test-Connection -Cn $computer -BufferSize 16 -Count 1 -ea 0 -quiet){
     
            try
   {
                Write-Host "Checking Software Updates Compliance on [$Computer]" -BackgroundColor Blue
            
                try 
                    {
                        $SiteCode = $([WmiClass]"\\$computer\ROOT\ccm:SMS_Client").getassignedsite()
                    }
                catch 
                    {
                        $SiteCode = "No Site Code Assigned"
                    }
                $ScanHistory = Get-WmiObject -Computer $Computer -Namespace root\ccm\scanagent -Class CCM_scantoolhistory -ErrorAction Stop
                $AgentGUID = Get-WmiObject -Computer $Computer -Namespace root\ccm -Class CCM_Client -ErrorAction Stop
                $ScanSource = Get-WmiObject -Computer $Computer -Namespace root\ccm\SoftwareUpdates\WUAhandler -Class CCM_UpdateSource -ErrorAction Stop
                $OSVer = Get-WmiObject -Computer $Computer -Namespace root\cimv2 -Class Win32_OperatingSystem -ErrorAction Stop
                $InstalledComponents = Get-WmiObject -Computer $Computer -Namespace root\ccm -Class CCM_InstalledComponent -ErrorAction SilentlyContinue
                $HighestComponentVer = $InstalledComponents | Sort version | Select version -last 1
                $Explorer = Get-WmiObject -Class win32_process -Computer $Computer -Filter "Name = 'explorer.exe'" | Select -first 1 -ErrorAction SilentlyContinue
                $LastBootUpTime = $osver.ConvertToDateTime($osver.LastBootUpTime)
                $SCCMservice = Get-Service -Computer $Computer ccmexec
                $WUservice = Get-Service -Computer $Computer wuauserv
                $BITSservice = Get-Service -Computer $Computer bits
                $PowerComponent = $InstalledComponents | Where {$_.Name -like '*Power*'}
                $WUVer = (Get-Command \\$Computer\c$\windows\system32\wuapi.dll).fileversioninfo
            
                if($PowerComponent.Version -like '*2157*'){$R3 = "YES"}else{$R3 ="NO"}

                try
           {
                        $User = $Explorer.getowner()
                    }
                catch
                    {
                    $User = "No User Logged On"
                    }

                $ScanHistory | Select @{Name="SCCM Assigned Site Code";Expression={$SiteCode.sSiteCode}},
                @{Name="Highest Agent Component Version";Expression={$HighestComponentVer.Version}},
                @{Name="Windows Update Agent Version";Expression={$WUVer.ProductVersion}},
                @{Name="Is R3 Installed";Expression={$R3}},
                @{Name="Agent GUID";Expression={$AgentGUID.ClientId}},
                @{Name="Logged On User";Expression={$User.Domain,$User.User}},
                @{Name="Last Boot Time";Expression={$LastBootUpTime}},
                @{Name="Last Scan Time";Expression={$_.ConvertToDateTime($_.LastCompletionTime)}},
                @{Name="WSUS CAB Version";Expression={$ScanHistory.ToolContentVersion}},
                @{Name="WSUS Scan CAB Source";Expression={$scanSource.Contentlocation}},
                @{Name="Windows Update Service";Expression={$WUservice.Status}},
                @{Name="SMS Agent Host Service";Expression={$SCCMservice.Status}},
                @{Name="BITS Transfer Service";Expression={$BITSservice.Status}},
                @{Name="Operating System";Expression={$OSver.Caption}}

   #check if the machine has an update assignment targeted at it
   $UpdateAssigment = Get-WmiObject -Query "Select * from CCM_AssignmentCompliance" -Namespace root\ccm\SoftwareUpdates\DeploymentAgent -Computer $Computer -ErrorAction Stop
                $statusHash = [hashtable]@{
                    "0" = 'No Content Sources'
                    "1" = 'Available'
                    "2" = 'Submitted'
                    "3" = 'Detecting'
                    "4" = 'Downloading CIDef'
                    "5" = 'Downloading SdmPkg'
                    "6" = 'PreDownload'
                    "7" = 'Downloading'
                    "8" = 'Wait Install'
                    "9" = 'Installing'
                    "10" = 'Pending Soft Reboot'
                    "11" = 'Pending Hard Reboot'
                    "12" = 'Wait Reboot'
                    "13" = 'Verifying'
                    "14" = 'Install Complete'
                    "15" = 'State Error'
                    "16" = 'Wait Service Window'
            }   

            $DPLocalityHash = [hashtable]@{
                    "10" = 'UNPROTECTED DP'
                    "74" = 'PROTECTED DP'
                
            }   

   #if update assignments were returned check to see if any are non-compliant
   if($UpdateAssigment)
   {
   $IsCompliant = $true
   $UpdateAssigment | ForEach-Object{
   Write-Host "Update Assignment: $($_.AssignmentId) : " -NoNewline
   if($_.IsCompliant -eq $true){Write-Host "Compliant" -ForegroundColor Green}else{Write-Host "Non-Compliant" -ForegroundColor Red}
   #mark the compliance as false
   if($_.IsCompliant -eq $false -and $IsCompliant -eq $true){$IsCompliant = $false}
   }
   }
   else
   {
   Write-Host "No Software Update Assignment targeted!" -ForegroundColor Yellow
   return
   }
   #the machine is not compliant; search for the updates
   if($UpdateAssigment -and $IsCompliant -eq $false)
   {
   #check if the machine has any targeted updates that are missing
   $TargetedUpdates = Get-WmiObject -Query "Select * from CCM_TargetedUpdateEX1 where UpdateState = 0" -Namespace root\ccm\SoftwareUpdates\DeploymentAgent -Computer $Computer -ErrorAction Stop
   if($TargetedUpdates)
   {
   $iMissing=0
   $UpdatesMissing=@()



   #loop through updates and get the details.
   $TargetedUpdates | ForEach-Object {

   #get the GUID
   $uID=$_.UpdateID | Select-String -Pattern "SUM_[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}" | Select -Expand Matches | Select -Expand Value
   #strip out the SUM_
   $uID=$uID.Remove(0,4)
   $uBulletinID=""
   $uTitle=""
                            $uDPLocality = $DPLocalityHash[$_.DPLocality.tostring()]
                            $uPercentComplete = $_.PercentComplete
                            $uStatus=$statusHash[$_.UpdateStatus.tostring()]
                        
                            #[decimal]$StatusNo = $_.UpdateStatus
                            #$uStatus=$statusHash.ContainsValue($StatusNo)
   #query the update status from WMI
   Get-WmiObject -Query "Select * from CCM_UpdateStatus where UniqueID = '$($uID)'" -Namespace root\ccm\SoftwareUpdates\UpdatesStore -Computer $Computer | ForEach-Object {
   $iMissing++
   $uBulletinID = $_.Bulletin
   #if there is no MS00-000 ID swap it for the KB article number
   if($uBulletinID -eq ""){$uBulletinID="KB$($_.Article)"}
   $uTitle=$_.Title
                            
   }
   #Write-Host "[$uBulletinID] :: [$uTitle]"
   $UpdatesMissing+= "[$uBulletinID] :: [$uTitle]::[$uDPLocality]::[%$uPercentComplete]::[$uStatus]"
   }
   Write-Host "[$iMissing] required security updates are missing:"-ForegroundColor Red
   #resort the array of missing updates and return it
   $UpdatesMissing=$UpdatesMissing | Sort-Object -Descending
   return $UpdatesMissing
   }
   }
   #machine is targeted and compliant
   else
   {
   Write-Host "No Missing Software Updates found." -ForegroundColor Green
   }
  
   }

   catch
   {
   throw
   }

   }
    else
        {
             Write-Host "" 
             Write-Host $($Computer).ToUpper()"is NOT Online." -ForegroundColor DarkYellow
             Write-Host ""
        }

        }

    }

}

Wednesday, 7 November 2012

PowerShell - Error when attempting to enable PS remoting.

Whilst enabling PowerShell remoting on our SCCM site servers I encountered this error.

Error:
Set-WSManQuickConfig : The WinRM client cannot process the request. It cannot determine the content type of the HTTP re
sponse from the destination computer. The content type is absent or invalid.
At line:50 char:33
+             Set-WSManQuickConfig <<<<  -force
    + CategoryInfo          : InvalidOperation: (:) [Set-WSManQuickConfig], InvalidOperationException
    + FullyQualifiedErrorId : WsManError,Microsoft.WSMan.Management.SetWSManQuickConfigCommand


Solution
1 \ Set the following two registry keys.
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\HTTP\Parameters
MaxFieldLength - 65534
MaxRequestBytes - 16777216
2\ Restart the server.


Friday, 5 October 2012

PowerShell - Get the assigned site code for an SCCM Agent.

Below is a useful PowerShell code snippet that I just worked out today. In the output of some of my scripts to view the current configuration of SCCM agent remotely I wanted to display the current assigned site code. In this example I am calling the getassignedsite code WMI method however this same code line could be reused for any of the methods in the class.

$([WmiClass]"\\ComputerName\ROOT\ccm:SMS_Client").getassignedsite() | Select sSiteCode


Available "SMS_Client" class Methods:

  • ResetPolicy
  • RequestMachinePolicy
  • EvaluateMachinePolicy
  • TriggerSchedule
  • RepairClient
  • SetAssignedSite
  • GetAssignedSite
  • SetGlobalLoggingConfiguration
  • ResetGlobalLoggingConfiguration
  • SetClientProvisioningMode
  • PDPMaintenanceTask


Ben

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.


Monday, 9 July 2012

PowerShell - Get AD user group memberships

Here is just a quick post on how to retrieve the AD group membership list for an AD user. The following commands need to be run on a Windows 7 or Server 2008 and above operating systems where the RSAT tools are installed.

  1. Import-module ActiveDirectory
  2. (GET-ADUSER –Identity ben.morris –Properties MemberOf | Select-Object MemberOf).MemberOf
Tip: Pipe to Out-GridView for easier reading.

Saturday, 23 June 2012

Checking BITs health on SCCM site servers with PowerShell

On a number of occasions I discovered that the BITs configuration in IIS has become corrupt resulting in SCCM agents being unable to submit new software and\or hardware inventory. There are no errors reported in any client logs, in fact the client logs give the impression that the MIF files were submitted to the site server successfully. If you enable IIS logging on the site server you will see error 501 log entries appear. This issue can also be confirmed by trying to view the BITs settings for the default website in IIS. The purpose of this script is to assist in easy identification of MP's where BITs is broken.

How to manually check BITs on an MP with PowerShell command:
  1. Open PowerShell as an administrator from the start-menu.
  2. Start-BitsTransfer -source .\Test-Bits.txt -dest http://[SCCMServerMP]/CCM_Incoming/test-bits.txt -TransferType Upload

Note: The above command requires a text file created locally which can be copied to the site server. If BITs is broken on the SCCM site server then the transfer will fail. You should manually delete the copied text file from the CCM_Incoming folder on the site server after the test is complete.

How to check multiple MP's with a PowerShell script:
  1. Copy the script to any local folder.
  2. Open PowerShell  as an administrator from the start-menu.
  3. Type ‘Set-executionpolicy remotelysigned’ and agree to prompt with 'Y’
  4. Run the script from any location using an explicit path.
                  e.g. ‘help .\Test-SCCMBitsTransfer.ps1  -full' to view syntax examples.

Note: To view script information and syntax type in the PowerShell shell:
              'help  Test-SCCMBitsTransfer.ps1 -full

Solution to a broken BITs client on a MP (link)
Fix MP fails to re-install on a site Reset
501 Errors found in IIS logs: 
Error seen in IIS when checking BITs settings:
Running the PowerShell Script:


PowerShell Script: Test-SCCMBitsTransfer.ps1
https://docs.google.com/open?id=0B2PiqIWBY0KsT2ZjMkZZMmxnZ1E

Below are links to a couple of my other blog posts that describe the method I would recommend to correct management point or BITs errors.

Fix MP fails to re-install on a site Reset
http://www.blogger.com/blogger.g?blogID=3820049899844331410#editor/target=post;postID=9068478152764659215


SCCM - Unable to enable BITs on an IIS virtual directory.
http://www.blogger.com/blogger.g?blogID=3820049899844331410#editor/target=post;postID=4941805560145335979







SCCM - Unable to enable BITs on an IIS virtual directory.


As a result of an issue I encountered recently with an SCCM 2007 primary site server it should be noted that if BITs cannot be enabled on IIS virtual directories then the following additional steps should be used prior to the reboot. The issue I was working with started out as investigating why SCCM client hardware inventory was not being recorded on site server. In the end it was found to be that because BITs on the server was corrupt it resulted in SCCM agents being unable to upload inventory MIF files.

1. Run ccmdelcert.exe from the SMS 2003 resource kit to remove SCCM related certificates.
2. Delete the contents of the following folders:
"C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\S-1-5-18\".
"C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys".
3. Reboot
4. Create a test IIS virtual directory and enable BITs transfers to confirm IIS can use BITs successfully.

Related Web Articles:
You receive error message 0x80090016 or error message 0x8009000f when you try to schedule a task
http://support.microsoft.com/kb/246183
Quote:
“6. Delete all of the files in the "C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\S-1-5-18" folder.
7. Reboot”

0x80090016: Keyset does not exist
http://chandusb1.blogspot.com.au/2011/03/0x80090016-keyset-does-not-exist.html

Fix MP fails to re-install on a site Reset

Issue:
The MP installation failed on the site server after the a site reset with provider was performed.

Analysis and solution:
The following errors were recorded in the mpMSI.log on the site server when attempting to install\reinstall the management point.

MSI (s) (AC!CC) [09:41:17:095]: Product: SMS Management Point -- Error 25006. Setup was unable to create the Internet virtual directory CCM_Incoming
The error code is 800CC801


Error 25006. Setup was unable to create the Internet virtual directory CCM_Incoming
The error code is 800CC801
CustomAction CcmCreateIISVirtualDirectories returned actual error code 1603 (note this may not be 100% accurate if translation happened inside sandbox)
MSI (s) (AC:64) [09:41:17:114]: User policy value 'DisableRollback' is 0
MSI (s) (AC:64) [09:41:17:114]: Machine policy value 'DisableRollback' is 0
Action ended 9:41:17: InstallFinalize. Return value 3.

Perhaps due to a previous SMS agent host service crash and server unexpected shutdown, the BITs extension may be corrupted on the site server. This also prevents a MP being re-installed and would also prevent clients successfully reporting updates compliance.

Performing  the below actions fixed the issue:
1. Uninstall the failed MP role from the SCCM console
2. Uninstall the SCCM agent from the site server.
3. Remove the BITs feature from the Server Manager console.
4. Reboot the server.
5. Install the Windows BITs feature.
6. Install the MP - This time it should install successfully.
7. Install the SCCM agent.
  • Afterwards the clients were able to communicate properly with the MP, receive policy and report updates compliance status
  • Please note: This is also a fix for some obscure client policy or client updates compliance issues or any situation where Bits may be suspect.
  • Also please note: The MP appeared to be healthy according to MP control log prior to attempting the site reset with 200 (healthy) status.
  • This remediation process is what I follow any time there is any recurring issues with a site servers management point or SCCM agent. 


Monday, 18 June 2012

PowerShell Script to copy and verify smsdef.mof files.

In an effort to simply the tedious task of updating the MOF files on SCCM site servers I have written a PowerShell script which will perform a MD5 HASH comparison of your MOF file between the target server and its child servers. The script will use a SQL query to determine the child site server list and their install folders. When the file HASHes being compared differ you will be prompted to overwrite the incorrect file with the file from the target server. The output of the script is a table displaying all the file HASHes generated for visual comparison. If you choose to overwrite any files be sure to run the script a second time to re-check all the file HASHes. By default the script only checks the SMS_DEF.MOF, an alternate filename can be specified with the '-MOFFile' parameter. Full help and syntax examples can be seen by running the help option below. Any feedback or suggestions are always appreciated.

Steps:
1. Copy the script to any local folder.
2. Open PowerShell with your Admin account from the startmenu.
3. Type ‘Set-executionpolicy RemoteSigned’ and agree to prompt with 'Y’
4. Run the script from any location using an explicit path.
                  e.g. ‘help .\SCCM_MOF_Replicate.ps1 -full' to view syntax examples.

Note: To view script information and syntax type 'help SCCM_MOF_Replicate.ps1 -full'

Running the script:

PowerShell Script:
https://docs.google.com/open?id=0B2PiqIWBY0KsSnZRVXhNNVRGajA

Sunday, 17 June 2012

PowerShell - Scripted Install of SCCM site server prerequisites


To reduce the time it takes to provision a 2008 SCCM site server I have improved on another internet sourced PowerShell script to automate all the set-up steps detailed in the TechNet article linked below.

How to Configure Windows Server 2008 for Configuration Manager 2007 Site Systems
http://technet.microsoft.com/en-us/library/cc431377.aspx

PowerShell Script:
https://docs.google.com/open?id=0B2PiqIWBY0KsWlAzVnd3MGJHUWM

Run Steps:
  1. Copy the script a local folder.
  2. Open PowerShell as an Administrator from the start menu.
  3. Type ‘Set-ExecutionPolicy RemoteSigned’ and agree to prompt with ‘Y’
  4. Run the script from any location using an explicit path. 
i.e. ‘.\Install_SCCM_Pre-Reqs.ps1’ if run from the current folder.


Install progress display output when run on a new 2008 server.
 

Script output when rerun subsequent times:

Friday, 15 June 2012

How to delete orphaned & locked SCCM folders

Description:
You are unable to delete orphaned locked console folders on a mid tier site that is also not visible at its parent site.

Analysis and solution:
A "locked" object in SCCM means that the object was created on a parent site so cannot be edited at a child primary site as indicated by a padlock on the console object. This is determined by the SourceSite value for the CI in the SCCM database at each primary site in the hierarchy.

For orphaned CI's that are not visible at its parent site, it is possible to manually set their SourceSite value to the site code where the CI is visible. It can then be modified\deleted in the console.

How to 'unlock' a CI in the SCCM database:
1. Perform a successful SCCM site backup.

2. Use the following SQL commands to update the folder CI in the database with the site code for the current site. This will 'unlock' the CI and allow it to be deleted or modify it in the console. Any changes made to the newly unlocked CI should replicate to child primary sites.

Declare @FolderName varchar( 32 )
Set @FolderName = 'My App Folder'
Declare @ParentSite varchar( 32 )
Set @ParentSite = 'ABC'


-- Before updating parent site code.
Select * from Folders where Name = @FolderName and ParentContainerNodeID = '0'
-- Updating parent side code.
-- Update Folders
-- Set SourceSite = @ParentSite --effected child primary site code
-- Where Name = @FolderName --effected folder name here
--and ParentContainerNodeID = '0' -- Effects only folders in the console root.
--After updating parent site code.
Select * from Folders where Name = @FolderName and ParentContainerNodeID = '0'

If the wrong CI is updated that does exist at a parent site then the next time any change is made to the parent CI  then the CI's all child primary sites will be updated to mirror the top level CI.

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.


How to confirm software update supersedence\ expiry information outside of the SCCM console


Scenario:
Five KB's for bulletin MS10-087 could not be found in the SCCM software updates repository however they could be seen in the WSUS console on the SCCM ASUP. The updates found in the WSUS console did not clearly indicate that they were superseded and this cause confusion with operational teams. Initial investigation pointed to the updates for MS10-087 were not being sync'ed into the SCCM repository for an unknown reason.

Solution:
Through checking the Microsoft Update Catalog website it was confirmed that MS10-087 had been superseded by a number of later updates that were being actively deployed. The reason for the five MS10-087 updates missing from the SCCM repository was that some time ago the repository was recreated due to database corruption. At the time of the first sync with Microsoft windows update MS10-087 was already superseded so was excluded from the sync process.

Superseded updates will not be synced into the SCCM repository if the update CI doesn't already exist, if the CI does exist the sync process will update the database record to mark it as superseded. Superseded updates will not automatically be removed from the SCCM repository, they will just accumulate over time.

Expired updates will not be synced into the SCCM repository if the CI doesn't already exist, if the CI does exist it will update the database record to mark it as expired. The point of difference is expired updates will automatically be deleted from the SCCM repository 7 days after their state has changed to expired in the database. It is important to remove any expired updates from update lists, package and deployments to avoid orphaned database entries otherwise software updates CI replication to child sites could break.

How to check the status of an update on-line:

1. Open the Microsoft Update Catalog : http://catalog.update.microsoft.com

2. In the right corner you can input the bulletin ID (such as : MS10-087)

3. Click Search and you will got the updates return.

4. Click on Security Update for Microsoft Office XP (KB2289169)

5. Click on the Package Details, you can then view the supersedence information for this update.

Thursday, 14 June 2012

PowerShell - Get drive statistics WIN32_LogicalDisk

PowerShell Command
Get-wmiobject -computername $ServerName win32_logicaldisk -filter "drivetype='3'" | select @{Name="Computer Name";expression={$_.SystemName}},@{Name="Drive";expression={$_.deviceID}},@{Name="Volume Name";expression={$_.VolumeName}} ,@{Name="Free(GB)";expression={[math]::truncate($_.freespace/1.0GB)}},@{Name="% Free";Expression={[math]::truncate(($_.FreeSpace/$_.Size)*100)}}, @{Name="Total(GB)";expression={[math]::truncate($_.Size/1.0GB)}} | format-table -autosize


Note: Substitute 'localhost' with a remote system you want drive information for.


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-SCCMDriveSpace {

    [CmdletBinding()]
    PARAM
    (
        [Parameter(Mandatory=$true, HelpMessage="SCCM Site Server",ValueFromPipeline=$true)] $ServerName
    )   
            Get-wmiobject -computername $ServerName win32_logicaldisk -filter "drivetype='3'" | select @{Name="Computer Name";expression={$_.SystemName}},@{Name="Drive";expression={$_.deviceID}},@{Name="Volume Name";expression={$_.VolumeName}} ,@{Name="Free(GB)";expression={[math]::truncate($_.freespace/1.0GB)}},@{Name="% Free";Expression={[math]::truncate(($_.FreeSpace/$_.Size)*100)}}, @{Name="Total(GB)";expression={[math]::truncate($_.Size/1.0GB)}} | format-table -autosize
 }

Note: I have named this function SCCM so as to group it in Powershell help results with other SCCM Powershell tools that have been created for the support teams.

Monday, 11 June 2012

Remotely search ARP for SCCM hotfixes with PowerShell

Here is a quick PS command I created to search a remote SCCM site server to check which hotfixes we had already installed. In this instance I was just reviewing a single server however it could easily be extended by first reading a list of server names from a text file with the get-content cmdlet.


Get-wmiobject -computername server01 -Class SMS_InstalledSoftware -namespace root\cimv2\sms | where {$_.ARPDisplayName -like "*configmgr*"} | format-wide



Saturday, 9 June 2012

MP Reg: Failed to get client(GUID:) public key: 0x80040238

Recently I thought I had an issue with SCCM clients being unable to register normally when initially installed. I had noticed the following error in the MP_RegistrationManager.log on the site server with the management point role installed and could not find any information on the web discussing this particular error. As a result I opened a case with Microsoft PSS and was informed that 0x80040238 can be safely ignored because it just represents a temporary status when a new client is trying to register. When a MP receives a new client registration request it will try to match the client public certificate hash (mixed or native mode environments)  to any already known in the database as a means of matching to an existing resource record. If there is no existing resource record with the same hash to match with then the following error will be recorded temporarily until the registration process is completed and a new resource record is created. Unfortunatily without verbose agent logging enabled you do not see the entire process including success entries in the mp_registrationmanager.log, only the error messages.


MP_RegistrationManager.log Error:
MP Reg: Failed to get client(GUID:) public key: 0x80040238


0x80040238 simply means the public key is not found. This error will almost always be reported when a new client is trying to register itself. Because the client is new, it is normal that no public key exists in the ClientKeyData table for that client. It is after that error is reported that the registration happens.

For example, here is a MP_RegistrationManager.log displaying successful registration where verbose agent logging has been enabled on the SCCM site server:

We can see the 0x80040238 error is just informational, as long as a second “Confirmation” of “ClientRegistrationResponse” can be received by the client. Here’s what the client’s ClientIDManagerStartup.log says at the same time:

Reference:How to enable verbose logging for the SCCM Agent.