Threaded Computer Details Aggregator

Overview

I created this application, Threaded Computer Details, to allow you to have a single point of data aggregation for common SCCM, DHCP and Active Directory metrics on the computers in your environment, and wrap those metrics around a convenient program with search options.

You can either search initially by computer name, or if User-Device Affinity is available in your environment, by user.

The application performs several initial network operations in a threaded hierarchy of ICMP -> RDP -> c$ -> SCCM queries -> AD Queries which provides efficiency when aggregating data on all of your machines.

Basic Use of Application

The application can be called directly, which will bring up a user interface for searching:


Get-DGMThreadedComputerDetails -ProviderMachineName SCCMSERVERNAME -Sitecode SITE -domaincontroller DOMAINCONTROLLER01

Or can be given input from the pipeline where it will not prompt for any interaction and would return the results as an array:

$mycomputerlist | Get-DGMThreadedComputerDetails -ProviderMachineName SCCMSERVERNAME -Sitecode SITE -domaincontroller DOMAINCONTROLLER01

It also supports switches for .CSV import and export via –csvimport and –csvexport, where both expect a path to a .CSV file.

As mentioned, the application returns the data as an array, which could easily be viewed in a GUI by piping the results to Out-Gridview as in this example:


$myservers | Get-DGMThreadedComputerDetails -ProviderMachineName SCCMSERVERNAME -Sitecode SITE -domaincontroller DOMAINCONTROLLER01 | Out-Gridview

Application Features

Given just a list of computer names, or a list of company user accounts, this program will attempt to find and return:

  • Name
  • Pingable Status
  • IP Address and IP Scope
  • AD Enabled/Disabled Status
  • RDP Enabled Status
  • Connect to c$ Status
  • VirtualCNO Status for Virtual Server Obects
  • SCCM Client Installation Status
  • Primary User Based On a Hierarchal Algorithm Including SCCM Pending User Device Affinity
  • User Source
  • User’s Company Title
  • User’s Company Department
  • User’s E-Mai
  • Other Users via User Device Affinity
  • Operating System
  • Manufacturer
  • Model
  • Serial Number
  • BIOS Version
  • Architecture (x86 / x64)
  • MAC Addresses
  • AD Description
  • DHCP Scope Location
  • AD Creation Date
  • AD Canonical Name
  • Last Time Online
  • Last Time Offline
  • Predetermined User

Getting Your Environment Ready for Threaded Computer Details 1.0

This application was written in PowerShell and needs to be run on a machine on your corporate network that has access to all the computers in your environment with ability to:

  • Ping (ICMP)
  • Check for RDP (3389)
  • Check for C$ share (if desired).

In addition, it needs access to read the DHCP scopes from your domain controller, and to do so needs the RSAT-DHCP tools installed:

Install-WindowsFeature -Name RSAT-DHCP 

In addition, it and also needs the SCCM Console installed on the computer it is run from, as it utilizes the SCCM modules to gather serial number, model, user-device affinity data, etc. To install the console, you could typically run something such as:

\\sccmserver\c$\Program Files\Microsoft System Center Configuration Manager\tools\ConsoleSetup\console.msi

Advanced Use of Application

If launched directly, the application provides you with several main search options:

[1] Search for single computer name:

Search for results on a single computer, such as MYPERSONALCOMPUTER01

[2] Search by wildcard computer name

Type a partial computer name and all computers matching those results will be returned, as in this example where we search for all computers with VDGMSC in the hostname:

[3] Search by description

Search via active directory description. Results will be returned for any computer containing the AD description query you specify.

[4] Search by Operating System

Partial matches work fine. For example, search for 2012 to return all of your Windows Sever 2012 computers. For this query, the OS is queried from Active Directory, not SCCM.

[5] Search by User Name (DOMAIN\USER)

If user-device affinity is enabled in your environment, you can search for what computers belong to what users.


[6] Import CSV (with PCs as ‘Name’ column)

Have a .CSV ready with a column header Name and underneath it with all of your computer names.

[7] Import CSV (with Users as ‘PredeterminedUser’ column)

Have a .CSV ready with a column header PredeterminedUser and underneath it with all of your user names in the format domain\user.

Use Case 1: Send Out an Automated E-Mail Report of All Important SCCM Non-Client Computers

Utilizing my HTML Email functions, here we import a list of non-compliant computers from SCCM and run them against the script. From there, we filter out computers than are non-enabled in AD, are not-pingable, etc. Then we break the list up by Server and Workstation and email it out.

Use Case Wrapper Function: Run-DGMSCCMNoClientReport.ps1


Import-Module "\\scriptserver\Scripts\Get-DGMThreadedComputerDetails\Get-DGMThreadedComputerDetails.psm1" -Force
Import-Module "\\scriptserver\scripts\New-DGMEmailReport\New-DGMEmailReport.psm1" -Force

function Run-DGMSCCMNoClientReport{
    param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$ProviderMachineName,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)]
        [ValidateLength(3,3)]
        [String]$Sitecode
    )

    #Create Some Arrays Of Data To Display in Report. You can create as many as you want.
    $OutputArrays = @()

    # Import the ConfigurationManager.psd1 module
    $module = "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1"
    if((Get-Module ConfigurationManager) -eq $null) {
        Write-Host Importing $module ...
        Import-Module $module -Force
    }

    # Connect to the site's drive if it is not already present
    if((Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue) -eq $null) {
        $NewPSDrive = New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $ProviderMachineName
    }

    # Set the current location to be the site code.
    Set-Location "$($SiteCode):\"

    $NonClients = Get-CMCollectionMember -CollectionName "All Non-Client Systems" | Select Name | Get-DGMThreadedComputerDetails -ProviderMachineName $ProviderMachineName -Sitecode $Sitecode -domaincontroller $domaincontroller
    $NonClientsWorkstations = $NonClients | Where-Object {$_."AD Enabled" -eq $true -and $_.Pingable -eq $true -and $_.VirtualCNO -ne $true -and $_."Operating System" -Clike "*Windows*" -and $_."Operating System" -cnotlike "*Server*"}
    $NonClientsServers = $NonClients | Where-Object {$_."AD Enabled" -eq $true -and $_.Pingable -eq $true -and $_.VirtualCNO -ne $true -and $_."Operating System" -Clike "*Windows*" -and $_."Operating System" -clike "*Server*"}

    #Array1
    $output = [PSCustomObject] @{
    'Message' = "These are all of the workstations that DO NOT HAVE THE SCCM CLIENT, however ARE PINGABLE and ENABLED IN AD. These workstations are not receiving Windows Updates or Software deployments from SCCM. 
    These workstations need to be investigated and potentially have the SCCM client installed manually. <b>Source Location:</b> \\scriptserver\c$\Program Files\Microsoft System Center Configuration  Manager\Client <b>Installation Command:</b> CCMSetup.exe /mp:SCCMSITESERVER /logon SMSSITECODE=AUTO";
    'Title' = "Non-Client Workstations";
    'Color' = "Red";
    'Array' = $NonClientsWorkstations | Select "Name","IP Address","RDP Enabled","Connect to c$","Operating System","Primary User","AD Description","DHCP Scope Location","AD Creation Date","Canonical Name" | Sort-Object -Property Name
    }
    if ($output.Array -ne $NULL){$OutputArrays+=$output}

    #Array2
    $output = [PSCustomObject] @{
    'Message' = "These are all of the SERVERS that DO NOT HAVE THE SCCM CLIENT, however ARE PINGABLE, ENABLED IN AD, and ARE NOT A VIRTUAL CNO. Please verify these servers are in the DMZ, etc..";
    'Title' = "Non-Client Servers";
    'Color' = "Red";
    'Array' = $NonClientsServers | Select "Name","IP Address","RDP Enabled","Connect to c$","Operating System","Primary User","AD Description","DHCP Scope Location","AD Creation Date","Canonical Name" | Sort-Object -Property Name
    }
    if ($output.Array -ne $NULL){$OutputArrays+=$output}

    #Multiple Arrays
    New-DGMEmailReport `
        -Arrays $OutputArrays `
        -ReportTitle "SCCM Non-Client Computers Report" `
        -from "SCCMDeployments@company.com" `
        -To "dmaiolo@company.com" `
        -subject "Report: SCCM Non-Clients That Are of Concern" `
        -AttatchResults 
}

Run-DGMSCCMNoClientReport -ProviderMachineName SCCMSITESERVER -Sitecode DGM

Use Case 2: Create a Comprehensive Computer Details Report of All Computers in Environment

Piping the SCCM “All Systems” collection as the input for the application, we can build a comprehensive details list of all equipment in the environment.

$ComprehensiveReport = Get-CMCollectionMember -CollectionName "All Systems" | Select Name | Get-DGMThreadedComputerDetails -ProviderMachineName $ProviderMachineName -Sitecode $Sitecode -domaincontroller $domaincontroller -csvoutput "C:\comprehensive_report.csv"

We could also run the data through an Excel PivotChart for some interesting metrics:

Use Case Wrapper Function: Run-DGMComprehensiveDetailsReport.ps1


Import-Module "\\scriptserver\Scripts\Get-DGMThreadedComputerDetails\Get-DGMThreadedComputerDetails.psm1" -Force
Import-Module "\\scriptserver\scripts\New-DGMEmailReport\New-DGMEmailReport.psm1" -Force

function Run-DGMComprehensiveDetailsReport{
    param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$ProviderMachineName,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)]
        [ValidateLength(3,3)]
        [String]$Sitecode
    )

    #Create Some Arrays Of Data To Display in Report. You can create as many as you want.
    $OutputArrays = @()

    # Import the ConfigurationManager.psd1 module
    $module = "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1"
    if((Get-Module ConfigurationManager) -eq $null) {
        Write-Host Importing $module ...
        Import-Module $module -Force
    }

    # Connect to the site's drive if it is not already present
    if((Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue) -eq $null) {
        $NewPSDrive = New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $ProviderMachineName
    }

    # Set the current location to be the site code.
    Set-Location "$($SiteCode):\"

    $ComprehensiveReport = Get-CMCollectionMember -CollectionName "All Systems" | Select Name | Get-DGMThreadedComputerDetails -ProviderMachineName $ProviderMachineName -Sitecode $Sitecode -domaincontroller $domancontroller -csvoutput "C:\comprehensive_report.csv"
}

Run-DGMComprehensiveDetailsReport -ProviderMachineName SCCMSERVER001 -Sitecode DGM 

Main Application: Get-DGMThreadedComputerDetails.psm1

This is the main application code.


<#
.Synopsis
   Get Computer Details
.DESCRIPTION
   The tool, Get-DGMThreadedComputerDetails.ps1, will build a detailed list of the computers or users you provide to it. 
.AUTHOR
   David Maiolo
#>

<#
.Synopsis
   Returns a True if name value is a Cluster Name Object/Virtual Computer Object
#>
function Get-DGMClusterComputerObject{
    [CmdletBinding()]
    Param
    (
        # Param1 help description
        [Parameter(
                   Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=0)]
        [ValidateScript({(Get-ADComputer -Identity $_).objectclass -eq 'computer' })]
        $Name

    )

    Begin
    {$Computer= Get-ADComputer $name -Properties serviceprincipalname
    }
    Process
    { If( $Computer.servicePrincipalName -contains 'DGMClusterVirtualServer/' + $Computer.Name){$result=$true}
      Else{$Result=$False}
    }
    End
    {$result
    }
}

Function Get-DGMThreadedComputerDetails{
    param(
        [Parameter(Position=0,Mandatory=$false,ValueFromPipeline=$true)]
        $input,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$false)]
        [String]$ProviderMachineName,
        [Parameter(Position=2,Mandatory=$true,ValueFromPipeline=$false)]
        [ValidateLength(3,3)]
        [String]$Sitecode,
        [Parameter(Position=3,Mandatory=$true,ValueFromPipeline=$false)]
        [String]$domaincontroller,
        [Parameter(Position=4,Mandatory=$false,ValueFromPipeline=$false)]
        [ValidateScript({(Test-Path $_)})]
        [String] $csvinput,
        [Parameter(Position=5,Mandatory=$false,ValueFromPipeline=$false)]
        [ValidateScript({($_ -le 100 -and $_ -gt 0)})]
        [int] $threads=100,
        [Parameter(Position=6,Mandatory=$false,ValueFromPipeline=$false)]
        [String] $csvoutput,
        [Parameter(Position=7,Mandatory=$false,ValueFromPipeline=$false)]
        [Switch] $excludeCNO
    )

    $path = (get-item -Path .).FullName
    $arraytoping=@()

    #Header
    Write-Host "==========================================" -ForegroundColor Cyan
    Write-Host "Get Threaded Computer Details" -ForegroundColor Cyan
    Write-Host "v1.0 (2018-02-14) by dmaiolo" -ForegroundColor Cyan
    Write-Host "==========================================" -ForegroundColor Cyan

    #Check for what arguments were passed
    if([bool]($MyInvocation.BoundParameters.Keys -match 'csvinput')){
        Write-Host "Importing $csvinput..."
        $csvimport = import-csv $csvinput
        <#
        $csvimport | foreach-object {
            $temparray = [PSCustomObject] @{'Name' = $_.Name}
            $arraytoping += $temparray 
            Write-Host "Importing"($_.Name)"..."
            }#>
        $arraytoping = $csvimport    
    }
    elseif([bool]($MyInvocation.BoundParameters.Keys -match 'input')){
        Write-Host "Importing from pipeline..."
        foreach ($object in $input) {
            $arraytoping += $object
        }
    }else{
        Write-Host "Manual Input Selected."
        $arraytoping = Get-DGMSearchQueryComputers -ProviderMachineName $ProviderMachineName -Sitecode $Sitecode
    }

    
    if ($arraytoping.count -lt 1){
        Write-Host "No Computers Were Found"
        break
    }


    if([bool]($MyInvocation.BoundParameters.Keys -match 'csvoutput')){
        $csvoutputcheck = $true
    }

    #Ping the computers
    $pingablecomputers = Get-DGMOnlineComputers -ComputerList $arraytoping -Threads $threads
    $pingablecomputers | add-member –membertype NoteProperty –name Pingable –Value True -Force

    #======================

    #Check RDP On the Pingable Computers
    $RDPEnableComputers = Get-DGMRDPComputers -ComputerList $pingablecomputers -Threads $threads
    $RDPEnableComputers | add-member –membertype NoteProperty –name RDPEnabled –Value True -Force

    #Create Non-RDP Enabled Computers Array
    $NonRDPEnabledComputers = $pingablecomputers | where {$RDPEnableComputers -notcontains $_}
    $NonRDPEnabledComputers | add-member –membertype NoteProperty –name RDPEnabled –Value False -Force

    #Combining RDP Enabled and Non-Enabled Computers into One Araay
    $RDPDetailedPingableComputers = @()
    $RDPDetailedPingableComputers+=$RDPEnableComputers
    $RDPDetailedPingableComputers+=$NonRDPEnabledComputers

    #======================


    #Check Path On the Pingable Computers
    $PathEnableComputers = Get-DGMTestPath -ComputerList $RDPDetailedPingableComputers -Threads $threads -Path "c$"
    $PathEnableComputers | add-member –membertype NoteProperty –name PathEnabled –Value True -Force

    #Create Non-Path Enabled Computers Array
    $NonPathEnabledComputers = $RDPDetailedPingableComputers | where {$PathEnableComputers -notcontains $_}
    $NonPathEnabledComputers | add-member –membertype NoteProperty –name PathEnabled –Value False -Force

    #Combining Path Enabled and Non-Enabled Computers into One Araay
    $PathRDPDetailedPingableComputers = @()
    $PathRDPDetailedPingableComputers+=$PathEnableComputers
    $PathRDPDetailedPingableComputers+=$NonPathEnabledComputers

    #======================

    #Create Non-Pingable Array
    $nonpingablecomputers = $arraytoping | where {$pingablecomputers -notcontains $_} 
    $nonpingablecomputers | add-member –membertype NoteProperty –name Pingable –Value False -Force
    $nonpingablecomputers | add-member –membertype NoteProperty –name RDPEnabled –Value $null -Force
    $nonpingablecomputers | add-member –membertype NoteProperty –name PathEnabled –Value $null -Force

    #Combine Everything into one giant final array
    $finalresults = @()
    $pingedresults = @()
    $pingedresults+=$PathRDPDetailedPingableComputers
    $pingedresults+=$nonpingablecomputers

   
    #Gather the DHCP Scope
    try{$dhcp_scopes = Get-DhcpServerv4Scope –ComputerName $domaincontroller}catch{Write-Host "No Access to Domain Controller $domaincontroller" -ForegroundColor Red;}

    $pingedresults | foreach-object {
       $Name = $_.Name
       $PredeterminedUser = $_.PredeterminedUser
       $RDPEnabled = $_.RDPEnabled
       $PathEnabled = $_.RDPEnabled
       
       #Note if CNO Object
       try{
        if((Get-DGMClusterComputerObject $Name) -eq $false) {$VirtualCNO = $null}else{$VirtualCNO = $TRUE}

       }catch{
        $VirtualCNO =$null
       }

        #If they were pingable, get the DHCP Scope and IP Address
        if ($_.Pingable -eq "True"){ 
            Write-Host "Processing Pinged $Name..."
            
            try{
                $ComputerObject = Get-ADComputer $Name -Properties Created,Description,OperatingSystem,CanonicalName,Enabled,extensionAttribute1,extensionAttribute2,extensionAttribute4,extensionAttribute5 | Select Name,Created,Description,OperatingSystem,CanonicalName,Enabled,extensionAttribute1,extensionAttribute2,extensionAttribute4,extensionAttribute5

                #Get IP Address
                if ($ComputerObject.IPv4Address){
                    $IPv4Address = ($ComputerObject.IPv4Address).ToString()

                    $IPv4Scope = (([ipaddress] $IPv4Address).GetAddressBytes()[0..2] -join '.').ToString()
                    $IPv4ScopesDescription = ($dhcp_scopes | Select-Object -Property Name,ScopeId | Where-Object ScopeId -Like "$IPv4Scope.*").Name
                }
                #If IP Was not in AD, Ping it and get it (mostly redundant at this point)
                elseif($IPv4Address = (Test-Connection -ComputerName $Name -Count 1 -ErrorAction SilentlyContinue).IPV4Address.IPAddressToString){
                    $IPv4Scope = (([ipaddress] $IPv4Address).GetAddressBytes()[0..2] -join '.').ToString()
                    $IPv4ScopesDescription = ($dhcp_scopes | Select-Object -Property Name,ScopeId | Where-Object ScopeId -Like "$IPv4Scope.*").Name
                }
                # No IP Found? Make these blank (IP should be found though, just a catch all
                else{
                    $IPv4Address = $null
                    $IPv4ScopesDescription = $null
                    $IPv4Scope = $null
                }
            }catch{
                $ComputerObject = $null
                $IPv4Address = $null
                $IPv4Scope = $null
                $IPv4ScopesDescription = $null
            }

        }
        #Since they are not pingable, don't bother with IP Address and Scope
        else{
            Write-Host "Processing Unpingable $Name..." -ForegroundColor Yellow
            try{
                $ComputerObject = Get-ADComputer $Name -Properties Created,Description,OperatingSystem,CanonicalName,Enabled,extensionAttribute1,extensionAttribute2,extensionAttribute4,extensionAttribute5 | Select Name,Created,Description,OperatingSystem,CanonicalName,Enabled,extensionAttribute1,extensionAttribute2,extensionAttribute4,extensionAttribute5
            }catch{
                $ComputerObject = $null
            }
            $IPv4Address = $null
            $IPv4ScopesDescription = $null
            $IPv4Scope = $null
            $RDOpen = $null
        }

        #Get AD Device Details
        if ($ComputerObject.Created){$Created = ($ComputerObject.Created).ToString()}else{$Created = $null}
        if ($ComputerObject.Description){$Description = ($ComputerObject.Description).ToString()}else{$Description = $null}
        if ($ComputerObject.OperatingSystem){$OperatingSystem = ($ComputerObject.OperatingSystem).ToString()}else{$OperatingSystem = $null}
        if ($ComputerObject.CanonicalName){$CanonicalName = ($ComputerObject.CanonicalName).ToString()}else{$CanonicalName = $null}
        if ($ComputerObject.Enabled){$Enabled = ($ComputerObject.Enabled).ToString()}else{$Enabled = $null}
        if ($ComputerObject.extensionAttribute1){$SME1 = ($ComputerObject.extensionAttribute1).ToString()}else{$SME1 = $null}
        if ($ComputerObject.extensionAttribute2){$SME2 = ($ComputerObject.extensionAttribute2).ToString()}else{$SME2 = $null}
        if ($ComputerObject.extensionAttribute4){$MW1 = ($ComputerObject.extensionAttribute4).ToString()}else{$MW1 = $null}
        if ($ComputerObject.extensionAttribute5){$MW2 = ($ComputerObject.extensionAttribute5).ToString()}else{$MW2 = $null}

        #Get SCCM Device Details
        try{
            $DeviceDetails = Get-DGMSCCMDeviceDetails -Hostname $Name -ProviderMachineName $ProviderMachineName -Sitecode $SiteCode

            $IsClient = $DeviceDetails.IsClient
            $CMUserName = $DeviceDetails.UserName
            $LastStatusMessage = $DeviceDetails.LastStatusMessage
            $LastSoftwareScan  = $DeviceDetails.LastSoftwareScan
            $LastPolicyRequest = $DeviceDetails.LastPolicyRequest
            $LastDDR = $DeviceDetails.LastDDR
            $LastClientCheckTime = $DeviceDetails.LastClientCheckTime
            $LastActiveTime  = $DeviceDetails.LastActiveTime
            $IsVirtualMachine = $DeviceDetails.IsVirtualMachine
            $CNLastOnlineTime  = $DeviceDetails.CNLastOnlineTime
            $CNLastOfflineTime = $DeviceDetails.CNLastOfflineTime
            $ResourceID = $DeviceDetails.ResourceID
        }catch{
            Write-Host "Warning: One of the standard details could not be determined. This probably means the machine is not in SCCM." -ForegroundColor cyan
            $IsClient = $null
            $CMUserName = $null
            $LastStatusMessage = $null
            $LastSoftwareScan  = $null
            $LastPolicyRequest = $null
            $LastDDR = $null
            $LastClientCheckTime = $null
            $LastActiveTime  = $null
            $IsVirtualMachine = $null
            $CNLastOnlineTime  = $null
            $CNLastOfflineTime = $null
            $ResourceID = $null
        }

        #Get Expanded SCCM WMI Device Details

        try{
            $ExpandedDeviceDetails = Get-DGMSCCMWMIHardwareDetails -ResourceID $ResourceID -ProviderMachineName $ProviderMachineName -Sitecode $SiteCode
            $SerialNumber = $ExpandedDeviceDetails.SMS_G_System_PC_BIOS.SerialNumber
            $BIOSVersion = $ExpandedDeviceDetails.SMS_G_System_PC_BIOS.SMBIOSBIOSVersion
            $Manufacturer = $ExpandedDeviceDetails.SMS_G_System_COMPUTER_SYSTEM.Manufacturer
            $Model = $ExpandedDeviceDetails.SMS_G_System_COMPUTER_SYSTEM.Model
            $Architecture = $ExpandedDeviceDetails.SMS_G_System_COMPUTER_SYSTEM.SystemType
            $MACAddresses = $ExpandedDeviceDetails.SMS_R_System.MACAddresses
        }catch{
            Write-Host "Warning: One of the expanded details could not be determined. This probably means the machine is not in SCCM." -ForegroundColor Yellow
            $SerialNumber = $null
            $BIOSVersion = $null
            $Manufacturer = $null
            $Model = $null
            $Architecture = $null
            $MACAddresses = $null
        }

        try{
            #Get User Device Affinity Details
            $DGMSCCMUsersOfDevice = Get-DGMSCCMUsersOfDevice -Hostname $Name -ProviderMachineName $ProviderMachineName -Sitecode $SiteCode
        }catch{
            $DGMSCCMUsersOfDevice = $null
        }

        #Get User Details
        
        if ($PredeterminedUser){
            try{$DGMUserDetails = Get-DGMUserDetails -UserName $PredeterminedUser}catch{$DGMUserDetails = $null}
            $PrimaryUser = $DGMUserDetails.DisplayName
            $Title = $DGMUserDetails.Title
            $Department = $DGMUserDetails.Department
            $EmailAddress = $DGMUserDetails.EmailAddress
            $UserSource = "Predetermined User"
        }
        elseif($SME1){
            try{$DGMUserDetails = Get-DGMUserDetails -DisplayName $SME1}catch{$DGMUserDetails = $null}
            $PrimaryUser = $DGMUserDetails.DisplayName
            $Title = $DGMUserDetails.Title
            $Department = $DGMUserDetails.Department
            $EmailAddress = $DGMUserDetails.EmailAddress
            $UserSource = "AD SME1 Attribute"
        }elseif ($CMUserName){
            try{$DGMUserDetails = Get-DGMUserDetails -UserName $CMUserName}catch{$DGMUserDetails = $null}
            $PrimaryUser = $DGMUserDetails.DisplayName
            $Title = $DGMUserDetails.Title
            $Department = $DGMUserDetails.Department
            $EmailAddress = $DGMUserDetails.EmailAddress
            $UserSource = "SCCM Hardware Association"
        }elseif ($DGMSCCMUsersOfDevice){
            $Users = $DGMSCCMUsersOfDevice
            foreach ($user in $users){ 
                $i++
                try{$DGMUserDetails = Get-DGMUserDetails -UserName $user}catch{$DGMUserDetails = $null}
                if ($i -eq 1){
                    $PrimaryUser = $DGMUserDetails.DisplayName
                    $Title = $DGMUserDetails.Title
                    $Department = $DGMUserDetails.Department
                    $EmailAddress = $DGMUserDetails.EmailAddress
                    $UserSource = "SCCM User-Device Affinity"
                
                 }
                else{
                    $SecondaryUser = $DGMUserDetails.DisplayName
                    $OtherUsers=$OtherUsers+$SecondaryUser+", "
            
                }
            $UserSource = "SCCM User-Device Affinity"
            }
        }elseif($Description -and ($DGMUserDetails = Get-DGMUserDetails -Description $Description)){
            #$DGMUserDetails = Get-DGMUserDetails -Description $Description
            $PrimaryUser = $DGMUserDetails.DisplayName
            $Title = $DGMUserDetails.Title
            $Department = $DGMUserDetails.Department
            $EmailAddress = $DGMUserDetails.EmailAddress
            $UserSource = "AD Description"
        }else{
            $OtherUsers = $null
            $PrimaryUser = $null
            $DisplayName = $null
            $Title = $null
            $Department = $null
            $EmailAddress = $null
            $UserSource = $null
        }

         $output = [PSCustomObject] @{
            'Name' = $Name;
            'Pingable' = $_.Pingable
            'IP Address' = $IPv4Address;
            'AD Enabled' = $Enabled;
            'RDP Enabled' = $RDPEnabled;
            'Connect to c$' = $PathEnabled;
            'VirtualCNO' = $VirtualCNO;
            'SCCM Client' = $IsClient;
            'Primary User' = $PrimaryUser;
            'User Source' = $UserSource;
            'User Title' = $Title;
            'User Department'  = $Department;
            'User E-Mail' = $EmailAddress;
            'Other Users' = $OtherUsers;
            'Operating System' = $OperatingSystem;
            'Manufacturer' = $Manufacturer;
            'Model' = $Model;
            'Serial Number' = $SerialNumber;
            'BIOS Version' = $BIOSVersion;
            '$Architecture' = $Architecture;
            'MAC Addresses' = $MACAddresses;
            'AD Description' = $Description;
            'DHCP Scope Location' = $IPv4ScopesDescription;
            'AD Creation Date' = $Created;
            #'Subnet' = $IPv4Scope;
            'Canonical Name' = $CanonicalName
            'Virtual Machine' = $IsVirtualMachine
            'Last Time Online' = $CNLastOnlineTime;
            'Last Time Offline' = $CNLastOfflineTime;
            'MW 1' = $MW1;
            'MW 2' = $MW2;
            'SME 1' = $SME1;
            'SME 2' = $SME2;
            'Predetermined User' = $PredeterminedUser;
        }

        $finalresults+=$output

       }

    #Export to CSV if chosen
        if ($csvoutputcheck){
            Write-Host =========================================== -ForegroundColor Cyan
            Write-Host CSV Output Results -ForegroundColor Cyan
            Write-Host =========================================== -ForegroundColor Cyan

            New-DGMCSVOut -csvoutputpath $csvoutput -arrayforoutput $finalresults 
        }

        #Remove-PSDrive "$SiteCode:"
        return $finalresults
        
}

Function Get-DGMUserDetails{
    [CmdletBinding()]
    [Alias()]
    Param
    (
        [Parameter(Mandatory=$false,ValueFromPipeline=$false,Position=0)] 
        [String]$UserName,
        [Parameter(Mandatory=$false,ValueFromPipeline=$false,Position=1)] 
        [String]$DisplayName,
        [Parameter(Mandatory=$false,ValueFromPipeline=$false,Position=2)] 
        [String]$Description
    )
    #Check for what arguments were passed
    if([bool]($MyInvocation.BoundParameters.Keys -match 'UserName')){
        #Remove Domain Name if found
        $UserName = $UserName -creplace '^[^\\]*\\', ''

        try{
            $UserObject = Get-ADUser $Username -Properties DisplayName,Title,Department,EmailAddress | Select -First 1

            $output = [PSCustomObject] @{
                'DisplayName' = $UserObject.DisplayName
                'Title' = $UserObject.Title
                'Department' = $UserObject.Department
                'EmailAddress' = $UserObject.EmailAddress;
            }

            return $output

        }catch{
            return $False
        }
    }
    #Check for what arguments were passed
    if([bool]($MyInvocation.BoundParameters.Keys -match 'DisplayName')){
        try{
            $UserObject = Get-ADUser -filter ('DisplayName -like "*' + $DisplayName + '*"') -Properties DisplayName,Title,Department,EmailAddress | Select -First 1

            $output = [PSCustomObject] @{
                'DisplayName' = $UserObject.DisplayName
                'Title' = $UserObject.Title
                'Department' = $UserObject.Department
                'EmailAddress' = $UserObject.EmailAddress;
            }

            return $output

        }catch{
            return $False
        }
    }

    #Check for what arguments were passed
    if([bool]($MyInvocation.BoundParameters.Keys -match 'Description')){
        
        #Replace bad charecters in the description with a space
        $description = $description -replace '-',' '

        #Split the string into an array
        $descriptionarray = $description -split " "

        #Clear Output
        $output = $null

        #Loop Through Every Sequential Two Word Combination in the description (looking for a first and last name) and see if there is a user associated to it in AD
        for ($i=0; $i -lt $descriptionarray.Length-1; $i++) {
            $DisplayName = $descriptionarray[$i]+" "+$descriptionarray[$i+1]
            
            $UserObject = Get-ADUser -filter ('DisplayName -like "*' + $DisplayName + '*"') -Properties DisplayName,Title,Department,EmailAddress | Select -First 1

            $output = [PSCustomObject] @{
                'DisplayName' = $UserObject.DisplayName
                'Title' = $UserObject.Title
                'Department' = $UserObject.Department
                'EmailAddress' = $UserObject.EmailAddress;
                }
            }

        if ($output.DisplayName -ne $null) {return $output}else{return $false}
    }
}

Function Get-DGMSCCMUsersOfDevice{

    [CmdletBinding()]
    [Alias()]
    Param
    (
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$Hostname,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$ProviderMachineName,
        [Parameter(Position=2,Mandatory=$true,ValueFromPipeline=$true)]
        [ValidateLength(3,3)]
        [String]$Sitecode
    )

    #Connect to SCCM
    # Import the ConfigurationManager.psd1 module
    $module = "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1"
    if((Get-Module ConfigurationManager) -eq $null) {
        Write-Host Importing $module ...
        Import-Module $module -Force
    }

    # Connect to the site's drive if it is not already present
    if((Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue) -eq $null) {
        $NewPSDrive = New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $ProviderMachineName
    }

    # Set the current location to be the site code.
    Set-Location "$($SiteCode):\"

    $CMUserDeviceAffinity = Get-CMUserDeviceAffinity -DeviceName $Hostname
    $users = $CMUserDeviceAffinity.UniqueUserName

    return $users
}

Function Get-DGMSCCMDeviceDetails{

    [CmdletBinding()]
    [Alias()]
    Param
    (
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$Hostname,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$ProviderMachineName,
        [Parameter(Position=2,Mandatory=$true,ValueFromPipeline=$true)]
        [ValidateLength(3,3)]
        [String]$Sitecode
    )

    #Connect to SCCM
    # Import the ConfigurationManager.psd1 module
    $module = "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1"
    if((Get-Module ConfigurationManager) -eq $null) {
        Write-Host Importing $module ...
        Import-Module $module -Force
    }

    # Connect to the site's drive if it is not already present
    if((Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue) -eq $null) {
        $NewPSDrive = New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $ProviderMachineName
    }

    # Set the current location to be the site code.
    Set-Location "$($SiteCode):\"
    try{
        $DeviceProperties = Get-CMDevice -Name $Hostname
        return $DeviceProperties
    }catch{
        return $FALSE
    }

}

function Get-DGMSearchQueryComputers{
    [CmdletBinding()]
    [Alias()]
    Param
    (
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$ProviderMachineName,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)]
        [ValidateLength(3,3)]
        [String]$Sitecode
    )

    
    #Connect to SCCM
    # Import the ConfigurationManager.psd1 module
    $module = "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1"
    if((Get-Module ConfigurationManager) -eq $null) {
        Write-Host Importing $module ...
        Import-Module $module -Force
    }

    # Connect to the site's drive if it is not already present
    if((Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue) -eq $null) {
        $NewPSDrive = New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $ProviderMachineName
    }

    # Set the current location to be the site code.
    Set-Location "$($SiteCode):\"

    Write-Host "[1] Search for single computer name"
    Write-Host "[2] Search by wildcard computer name"
    Write-Host "[3] Search by description"
    Write-Host "[4] Search by Operating System"
    Write-Host "[5] Search by User Name (DOMAIN\USER)"
    Write-Host "[6] Import CSV (with PCs as 'Name' column)"
    Write-Host "[7] Import CSV (with Users as 'PredeterminedUser' column)"

    do {
        try {$numOk = $true; [int]$GetMyANumber = Read-host "Selection"}
        catch {$numOK = $false}}
    until (($GetMyANumber -ge 1 -and $GetMyANumber -le 7) -and $numOK)

    $validcharacters = "^[a-zA-Z0-9\\\s-_@]+$"
    do {
        try {$stringOk = $true; [string]$query = Read-host "Enter search query (A-Z, a-z, 0-9,\,-,_,@ only)"}
        catch {$stringOk = $false}}
    until (($query -match $validcharacters) -and $stringOk)

    switch ($GetMyANumber) 
        { 
            1 {$finalresults = Get-ADComputer -filter {name -eq $query}}
            2 {$query = "*"+$query;$query = $query+"*";$finalresults = Get-ADComputer -filter {name -like $query}}
            3 {$query = "*"+$query;$query = $query+"*";$finalresults = Get-ADComputer -filter {description -like $query}}
            4 {$query = "*"+$query;$query = $query+"*";$finalresults = Get-ADComputer -filter {OperatingSystem -like $query}}
            5 {$finalresults = Get-CMUserDeviceAffinity -UserName $query | select @{name ="Name";e={$_.ResourceName}}}
            6 {$csvinputfile = Get-DGMFileName -filter "CSV";$csvinput = import-csv -Path $csvinputfile; $finalresults = $csvinput}
            7 {$csvinputfile = Get-DGMFileName -filter "CSV";$csvinput = import-csv -Path $csvinputfile; 
                $finalresults = @()
                foreach ($line in $csvinput){
                    $usercomputers = Get-CMUserDeviceAffinity -UserName $line.PredeterminedUser | Sort-Object -Property Sources | select @{name ="Name";e={$_.ResourceName}},Sources
                    foreach ($usercomputer in $usercomputers){
                        $output = [PSCustomObject] @{
                                    'Name' = $usercomputer.Name;
                                    'PredeterminedUser' = $line.PredeterminedUser;
                                    'Sources' = $usercomputer.Sources;
                                }
                        $finalresults+=$output
                   }
                }
                 $finalresults | Out-GridView -Title "User Affinity Preview. Still processing..."
              }
        }

    return $finalresults

}

function Get-DGMOnlineComputers{

    param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [int] $threads,
        [Array] $ComputerList
    )
    
    Write-Host ============================================ -ForegroundColor Cyan
    Write-Host Pinging Computers and Building Table -ForegroundColor Cyan
    Write-Host ============================================ -ForegroundColor Cyan
    
    $finalresults = @()

    if ($ComputerList.Length -gt 0){
        Write-Host "Pinging"($ComputerList.length)"computers in $threads threads."
        $finalresults = $ComputerList | Where-ParallelObject -Filter {Test-Connection -ComputerName $_.Name -Quiet -Count 1} -Threads $threads -ProgressBar -progressBartext "Buildng Computer Details"
        return $finalresults
    }else{
        Write-Host "No Pingable Computers were found."
        return $false
    }
}

function Get-DGMRDPComputers{

    param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [int] $threads,
        [Array] $ComputerList
    )
    
    Write-Host ============================================ -ForegroundColor Cyan
    Write-Host Testing RDP on Computers and Building Table -ForegroundColor Cyan
    Write-Host ============================================ -ForegroundColor Cyan
    
    $finalresults = @()

    if ($ComputerList.Length -gt 0){
        Write-Host "RDP Check on"($ComputerList.length)"computers in $threads threads."
        $finalresults = $ComputerList | Where-ParallelObject -Filter {Test-NetConnection -ComputerName $_.Name -CommonTCPPort RDP -InformationLevel Quiet -WarningAction SilentlyContinue} -Threads $threads -ProgressBar -progressBartext "Buildng RDP Return Details"
        return $finalresults
    }else{
        Write-Host "No RDP Enabled Computers were found."
        return $false
    }
}

function Get-DGMTestPath{

    param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [int] $threads,
        [Array] $ComputerList,
        [String]$path
    )
    
    Write-Host ============================================ -ForegroundColor Cyan
    Write-Host Testing Path on Computers and Building Table -ForegroundColor Cyan
    Write-Host ============================================ -ForegroundColor Cyan
    
    $finalresults = @()

    if ($ComputerList.Length -ge 0){
        Write-Host "Testing Path $path on"($ComputerList.length)"computers in $threads threads."
        $finalresults = $ComputerList | Where-ParallelObject -Filter {Test-Path $('filesystem::\\'+$_.Name+'\'+$path)} -Threads $threads -ProgressBar -progressBartext "Building Test-Path $path Details"
        return $finalresults
    }else{
        Write-Host "No Computers Returned a valid path."
        return $false
    }
}

function New-DGMCSVOut{

    param(
            [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
            [String] $csvoutputpath,
            [array] $arrayforoutput
        )

    try{
        $arrayforoutput | export-csv $csvoutputpath -notypeinformation
        Write-Host "CSV Export`: CSV Created at $csvoutputpath"
    }catch{
        Write-Host "CSV Export`: CSV Could NOT be Created at $csvoutputpath" -ForegroundColor Red
    }


}

function Where-ParallelObject {
    param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)] $input,
        [ScriptBlock] $Filter,
        [int] $threads,
        [switch] $progressBar,
        [String] $progressBartext
    )

    $inputQueue = [System.Collections.Queue]::Synchronized( (New-Object System.Collections.Queue) )
    $results = [System.Collections.Queue]::Synchronized( (New-Object System.Collections.Queue) )

    $sessionstate = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
    $sessionstate.Variables.Add(
        (New-Object System.Management.Automation.Runspaces.SessionStateVariableEntry('inputQueue', $inputQueue, $null))
    )
    $sessionstate.Variables.Add(
        (New-Object System.Management.Automation.Runspaces.SessionStateVariableEntry('results', $results, $null))
    )

    $runspacepool = [runspacefactory]::CreateRunspacePool(1, $threads, $sessionstate, $Host)
    $runspacepool.Open()

    foreach ($object in $input) {
        $inputQueue.Enqueue($object)
    }

    $jobs = @()

    $sbpre = '
        while($inputQueue.Count -gt 0) {
            $_ = $inputQueue.Dequeue();
            if('
    $sbpost = ') 
            {
                $results.Enqueue($_);    
            }
        }
    '

    $sb = [ScriptBlock]::Create($sbpre + $Filter.toString() + $sbpost)

    1..$threads | % {
        $job = [PowerShell]::Create().AddScript($sb)
        $job.RunspacePool = $runspacepool
        $jobs += New-Object PSObject -Property @{
            Job = $job
            Result = $job.BeginInvoke()
        }
    }

    do {
        if($progressBar.IsPresent) 
        {
            Write-Progress -Activity ($progressBartext+" " +$input.Count+ " Objects") -status ("" + $($results.Count) + " complete.") -percentComplete ( ($results.Count) / $input.Count * 100) 
        }
        Start-Sleep -Seconds 1
    } while ( $jobs.Result.IsCompleted -contains $false)

    foreach ($job in $jobs) {
        $job.Job.EndInvoke($job.Result)
    }
    $runspacepool.Close()
    $runspacepool.Dispose()

    return $results.ToArray()

}

function Get-DGMSCCMWMIHardwareDetails{
    [CmdletBinding()]
    [Alias()]
    Param
    (
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$ResourceID,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$ProviderMachineName,
        [Parameter(Position=2,Mandatory=$true,ValueFromPipeline=$true)]
        [ValidateLength(3,3)]
        [String]$Sitecode
    )
    #Change these to match your Server Name and Site Code
    $SCCMnameSpace = "root\SMS\SITE_$Sitecode"

    #Query WMI to get the 'Computer System Product' information
    $qry = "select * from SMS_R_System inner join SMS_G_System_COMPUTER_SYSTEM on SMS_G_System_COMPUTER_SYSTEM.ResourceID = SMS_R_System.ResourceId inner join SMS_G_System_PC_BIOS on SMS_G_System_PC_BIOS.ResourceID = SMS_R_System.ResourceId where ResourceID = '$ResourceID'"
    
    try{
        $objComputerSystemProduct = Get-WmiObject -ComputerName $ProviderMachineName -Namespace $SCCMnameSpace -Query $qry
        return $objComputerSystemProduct
    }catch{
        return $False
    }
}

Function Get-DGMFileName(){   
    [CmdletBinding()]
    [Alias()]
    Param
    (
        [Parameter(Position=0,Mandatory=$false,ValueFromPipeline=$true)]
        [String]$initialDirectory,
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$filter
    )
    [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") |
    Out-Null

    $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
    $OpenFileDialog.initialDirectory = $initialDirectory
    $OpenFileDialog.filter = "$filter files (*.$filter)| *.$filter"
    $OpenFileDialog.ShowDialog() | Out-Null
    $OpenFileDialog.filename
}

function Invoke-DGMThreadedComputerDetails{
    [CmdletBinding()]
    [Alias()]
    Param
    (
    )
    Write-Host "============================================"
    Write-Host "Get Threaded Computer Details"
    Write-Host "============================================"
    Write-Host "Author: dmaiolo"
    Write-Host "Instructions: Choose a CSV file where the column of computer names is 'Name'"
    $csvinput = Get-DGMFileName -filter "CSV"
    Get-DGMThreadedComputerDetails -ProviderMachineName SCCMSERVERNAME -Sitecode SITE -domaincontroller DOMAINCONTROLLER01 -csvinput $csvinput
}

Leave a Comment

Your email address will not be published.