Powershell

Force Evaluation of a Baseline

This script will trigger a specified baseline to evaluate on a computer using the run script function in SCCM. If no baseline is specified all baselines on the computer will be evaluated.

param (
    [String][Parameter(Mandatory = $False, Position = 1)] $BLName
)

$ComputerName = $env:COMPUTERNAME

If ($BLName -eq $Null) {
    $Baselines = Get-WmiObject -ComputerName $ComputerName -Namespace root\ccm\dcm -Class SMS_DesiredConfiguration
}
Else {
    $Baselines = Get-WmiObject -ComputerName $ComputerName -Namespace root\ccm\dcm -Class SMS_DesiredConfiguration | Where-Object { $_.DisplayName -like $BLName }
}
 
$Baselines | % {
 
 ([wmiclass]"\\$ComputerName\root\ccm\dcm:SMS_DesiredConfiguration").TriggerEvaluation($_.Name, $_.Version) 
 
}

Script to count computers in AD which have changed computer password within a certain period

This script will count the number of Windows computers (excluding servers) in Active Directory under the specified path which have changed computer password within the last 90 days.

When the script completes it will write the computer Count to the prompt and it will send an email with a csv file attached to the specified recipients.

Modify the following variables:

  • Smtpserver – Name of the smtpserver / relay
  • From – Mail address which should appear as sender (Could be the name of the server from which the mail is sent)
  • Recipient – Mail address of the recipient(s).
  • ComputerpasswordAgeDays – Number of days since the password was last changed.
  • ADSearchBase – OU to search for computers. DistinguishedName attribute from AD.
$m = Get-Module -List ActiveDirectory
if(!$m) {
    Write-Host "AD Powershell Module is not installed. Install it and run the script again."
    Exit
}

else{
    Import-Module ActiveDirectory
}


#Mail
$smtpServer = "ex01.domain.com"
$from = "MAN01@domain.com"
$recipient = "user@domain.com"

#Variables
[string]$SavePath = "$PSScriptRoot\reports"
[int]$ComputerPasswordAgeDays = 90
[string]$ADSearchBase = "DC=domain,DC=com"


#--------------------------------------
#Name: sendMail 
#Arguments: 2 Arguments:
#           1. Computer Count
#           2. File to be attached
#Return: -
#Description: Sends an E-mail to recipient defined in $recipient containing the csv file and computer count.
#--------------------------------------

function sendMail{

     $mailinfo_computer_count = $args[0]
     $file = $args[1]
  

    $body = "Computers which have changed computer password within the last $ComputerPasswordAgeDays days.</br></br>"
    $body += $mailinfo_computer_count

    $subject = "Antal computere: " + $mailinfo_computer_count
    
                 
     #Sending email 
     Send-MailMessage -SmtpServer $smtpServer -From $from -To $recipient -Subject $subject -Body $body -BodyAsHtml -Attachments $file
  
}


$Date = (Get-Date -format "MM-dd-yyyy")
IF ((test-path $SavePath) -eq $False) { md $SavePath }
$ExportFile = "$SavePath\Workstations-$Date.csv"
$ComputerStaleDate = (Get-Date).AddDays(-$ComputerPasswordAgeDays)
$Computers = Get-ADComputer -SearchBase $ADSearchBase -filter { (passwordLastSet -ge $ComputerStaleDate) -and (OperatingSystem -notlike "*Server*") -and (OperatingSystem -like "*Windows*") } -properties Name, DistinguishedName, OperatingSystem,OperatingSystemServicePack, passwordLastSet,LastLogonDate,Description
$ComputerTrimmed = $Computers.Count
write-output "Number of computers: " ($ComputerTrimmed) 

$Computers | export-csv $ExportFile -NoTypeInformation
sendMail $ComputerTrimmed $ExportFile

Script to extract all GPOs that a specific User, Group or Computer either have or doesnt have rights to

If you ever need to provide information about which GPO’s a specific User, Group or Computer have or doesnt have access to you can use this script.

The script will run through all GPO’s in the domain and check if a given User, Group or Computer either have or havent been delegated permissons. The script will produce a text file listing all GPO’s including detailed information about linkpaths, WMI Filters, Modify Dates, if its link or not and much more.

All you have to do is supply 3 parameters

  • TargetName – Name of the target (ie. Authenticated Users)
  • TargetType – Type of the target (Group, User or Computer)
  • hasPermissions – Boolean to specify if you want to find all the GPO’s that the target has permissions to (True) or does not have permissons to (False)

Example:

This will list all the GPO’s which the Group “Authenticated Users” doesnt have access to.

Get-GPOPermissionReport -TargetName “Authenticated Users” -TargetType Group -hasPermission -$False

<#
 
.SYNOPSIS
    Runs through all GPOs and checks if a given target has permissions and adds result to a text file. 
 
.DESCRIPTION
    This script will produce a text file containing all GPO details of the GPOs where the target either have or doesnt have permissions to. 
 
.PARAMETER TargetName
    Name of the target 
 
.PARAMETER TargetType
    Type of target (Group, User or Computer)

.PARAMETER hasPermissions
    Boolean to specify if we want to find all the GPO's that the target has permission to (True) or does not have permission to (False)
   
.EXAMPLE
    Get-GPOPermissionReport -TargetName "Authenticated Users" -TargetType Group -hasPermission $False
    Lists all the GPO's where Authenticated Users doesnt have access

.EXAMPLE
    Get-GPOPermissionReport -TargetName "Authenticated Users" -TargetType Group -hasPermission $True
    Lists all the GPO's where Authenticated Users have access
  
.NOTES
    Cmdlet name:      Get-GPOPermissionReport
    Author:           Mikael Blomqvist
    DateCreated:      2017-07-06
 
#>


[CmdletBinding(DefaultParameterSetName="String")] 
Param(
 [Parameter(Mandatory=$True, Position=0, HelpMessage="TargetName")] 
            [string]$TargetName,
 [Parameter(Mandatory=$True, Position=1, HelpMessage="TargetType(User/Computer/Group)")] 
            [string]$TargetType,
 [Parameter(Mandatory=$True, Position=2, HelpMessage="Find all GPO's where target has permission(True/False)")] 
            [boolean]$hasPermission
)


#Load GPO module
 Import-Module GroupPolicy
 
 $date = Get-Date -Format yyyyMMddhhmmss

 $file = "$PSScriptRoot\GPOPermissions_$date.csv"

 #Prepare the text file with the correct headings
 Add-Content $file "Name,LinksPath,WMI Filter,CreatedTime,ModifiedTime,CompVerDir,CompVerSys,UserVerDir,UserVerSys,CompEnabled,UserEnabled,SecurityFilter,GPO Enabled,Enforced"

#Get all GPOs in current domain
 $GPOs = Get-GPO -All

#Check we have GPOs
 if ($GPOs) 
 {
    foreach ($GPO in $GPOs) 
    {
        $GPOName = $GPO.DisplayName
        $GPOGuid = $GPO.id
        
        #Retrieve the permission for the specified target
        $GPPermissions = Get-GPPermissions -Guid $GPOGuid -TargetName $TargetName -TargetType $TargetType -ErrorAction SilentlyContinue

        #Check if target have permissions or not and set permission to either True(has permission) or False(no permission)
        If($GPPermissions -eq $null)
        { 
            $permission = $False
        }
        Else
        {
            $permission = $True
        }

        # if permissions matches what we look for either true or false
        if ($permission -eq $hasPermission) 
        { 
            [xml]$gpocontent =  Get-GPOReport -Guid $GPOGuid -ReportType xml
            $LinksPaths = $gpocontent.GPO.LinksTo
            $Wmi = Get-GPO -Guid $GPOGuid | Select-Object WmiFilter
 
            $CreatedTime = $gpocontent.GPO.CreatedTime
            $ModifiedTime = $gpocontent.GPO.ModifiedTime
            $CompVerDir = $gpocontent.GPO.Computer.VersionDirectory
            $CompVerSys = $gpocontent.GPO.Computer.VersionSysvol
            $CompEnabled = $gpocontent.GPO.Computer.Enabled
            $UserVerDir = $gpocontent.GPO.User.VersionDirectory
            $UserVerSys = $gpocontent.GPO.User.VersionSysvol
            $UserEnabled = $gpocontent.GPO.User.Enabled
            $SecurityFilter = ((Get-GPPermissions -Guid $GPOGuid -All | ?{$_.Permission -eq "GpoApply"}).Trustee | ?{$_.SidType -ne "Unknown"}).name -Join ','
        
            if($LinksPaths -ne $null)
            {
                foreach ($LinksPath in $LinksPaths)
                {
                    Add-Content $file "$GPOName,$($LinksPath.SOMPath),$(($wmi.WmiFilter).Name),$CreatedTime,$ModifiedTime,$CompVerDir,$CompVerSys,$UserVerDir,$UserVerSys,$CompEnabled,$UserEnabled,""$($SecurityFilter)"",$($LinksPath.Enabled),$($LinksPath.NoOverride)"
                }   
            }
            else
            {
                Add-Content $file "$GPOName,$($LinksPath.SOMPath),$(($wmi.WmiFilter).Name),$CreatedTime,$ModifiedTime,$CompVerDir,$CompVerSys,$UserVerDir,$UserVerSys,$CompEnabled,$UserEnabled,""$($SecurityFilter)"",$($LinksPath.Enabled),$($LinksPath.NoOverride)"
            } 
        }
    }
}

Script to Copy Machine Variables from one computer to another

I recently had to create several computers in Configuration Manager in order to pre-stage them for an OSD Deployment. As the OSD Deployment is dependent on the computers having certain machine variables assigned i had to add these for every computer too. I wanted all the new computers to have exactly the same variables as an existing computer. Instead of doing that manually for every computer i came up with this script which will do it automatically.

The script takes as input the Site Code, Source Computer and Target Computer.

Source Computer is just the name of the computer in Configuration Manager that has the variables you want to copy. Target Computer can be the name of one or more Target Computers either supplied as a string of computers or as a text file where each line hold a computer name.

In this example:

Copy-MachineVariables -Source IT-14 -Target IT-13, IT-15

All machine variables will be copied from computer IT-14 to computers IT-13 and IT-15

See more examples in the code below. Enjoy!

<#
  
.SYNOPSIS
    Copy machine variables from one Configuration Manager Computer Object to one or more computers.
 
.DESCRIPTION
    By giving the name of a Source computer all machine variables from that computer will be gathered and copied to one or more 
    of the target computers specified. Target Computers can be supplied either as a string or as seperate lines in a file. 
    Existing machine variables on the target computer(s) will as default not be removed as the new variables will just be appended.
    By setting Replace to $True exsisting variables on the target computer(s) will be removed.
 
.PARAMETER Source
    Name of the computer from which the machine variables should be copied
 
.PARAMETER Target
    Name of the computer(s) to which the machine variables should be copied

.PARAMETER TargetFile
    Path to file containing name of one or more computer(s) to which the machine variables should be copied
 
.PARAMETER Replace
    Decides if machine variables on the target computer(s) should be replaced. By default variables will be appended. 
    If this is set to True all exsisting machine variables on the target computer(s) are overwritten.
  
.EXAMPLE
    Copy-MachineVariables -Source IT-14 -Target IT-13
    Copy all machine variables on computer IT-14 and append them to computer IT-13

.EXAMPLE
    Copy-MachineVariables -Source IT-14 -Target IT-13, IT-15
    Copy all machine variables on computer IT-14 and append them to computers IT-13 and IT-15
 
.EXAMPLE
    Copy-MachineVariables -Source IT-14 -Target IT-13 -OverWrite $True
    Copy all machine variables on computer IT-14 and replace them with all exsiting variables on computer IT-13

.EXAMPLE
    Copy-MachineVariables -Source IT-14 -TargetFile C:\TargetComputers.txt"
    Copy all machine variables on computer IT-14 and append them with all exsiting variables on computers in C:\TargetComputers.txt
 
.NOTES
    Cmdlet name:      Copy-MachineVariables
    Author:           Mikael Blomqvist
    DateCreated:      2017-05-26
 
#>


[CmdletBinding(DefaultParameterSetName="String")] 
Param(
 [Parameter(Mandatory=$True, Position=0, HelpMessage="Supply Site Code")] 
            [string]$SiteCode,
 [Parameter(Mandatory=$True, Position=1, HelpMessage="Computer name(s) of source computer(s)")] 
            [string]$Source,
 [Parameter(Mandatory=$True, Position=2, ParameterSetName="String", HelpMessage="Computer name(s) of target computer(s)")] 
            [string[]]$Target,
 [Parameter(Mandatory=$True, Position=2, ParameterSetName="File", HelpMessage="File containing Computer name(s) of target computer(s)")] 
            [string]$TargetFile,
 [Parameter(Mandatory=$False, ParameterSetName="String",HelpMessage="Replace current machine variables on target computers - if false then variables will be appended")] 
 [Parameter(Mandatory=$False, ParameterSetName="File",HelpMessage="Replace current machine variables on target computers - if false then variables will be appended")] 
            [boolean]$Replace=$False
 
)



Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1" # Import the ConfigurationManager.psd1 module 
Set-Location $SiteCode':' # Set the current location to be the site code.

$provider = Get-PSDrive -PSProvider CMSITE
$siteServer = $provider.Root

#Getting Machine Variables from Source Computer
Write-Host "Gathering machine variables from $Source"
$objSourceSettings = Get-WmiObject -Namespace "Root\SMS\Site_$SiteCode" -Class "SMS_MachineSettings" -ComputerName $siteServer | Where-Object {$_.ResourceID -eq (Get-CMDevice -Name $Source).ResourceID}
$objSourceSettings.get()
$variablesSource = $objSourceSettings.MachineVariables

try{
    If($TargetFile -ne $null){
    #Reading all computers from file
    $Target = Get-Content($TargetFile)
    }
}
catch{
  #No file supplied
}
#Writing Machine Variables to Target Computers
Foreach($t in $Target){
   
    $objTargetSettings = Get-WmiObject -Namespace "Root\SMS\Site_$SiteCode" -Class "SMS_MachineSettings" -ComputerName $siteServer | Where-Object {$_.ResourceID -eq (Get-CMDevice -Name $t).ResourceID}
   

    If($Append -eq $False){
    Write-Host "Appending variables to $t"    
        $objTargetSettings.Get()
        $variablesTarget = $objTargetSettings.MachineVariables
    }
    Else{
    Write-Host "Overwriting existing variables to $t"

    }

    #$variablesTarget

    foreach($variable in $variablesSource)
    {
       $newVar = $([wmiclass]"\\$siteServer\Root\SMS\Site_$($SiteCode):SMS_MachineVariable").CreateInstance()
       $newVar.Name = $variable.Name
       $newVar.Value = $variable.Value
       [System.Management.ManagementBaseObject[]]$variablesTarget += ($newVar)
    }

    $objTargetSettings.MachineVariables = $variablesTarget
    $objTargetSettings.Put()
}

Write-Host "Done"

Script to create PC Environment Report

This Script generates a dynamic PC Environment Report. The report can include various information from Configuration Manager and Active Directory depending on your needs. There is an option to send this report as a mail to the specified recipient(s).

The information that can be collected from Configuration Manager is:

  • Patch Compliance overview showing a given past number of Software Update Groups (default 3 Groups)
  • Application categorization showing the number of applications that are either superseeded, without deployment or expired
  • Devices categorization showing the number of devices that are active, inactive, obsolete and without a configuration manager client
  • Computer reboot overview showing how many computers that have not been rebooted within a given time period (default 5 days)

The information that can be collected from Active Directory:

  • Computer password information – how many computers that have and have not changed their computer password within a given time period (default 90 days)
  • Operating system Overview – showing how many different Operating System versions that are present in the domain and how many there are of each
<#

.SYNOPSIS
    Returns a environment status report within the areas that have been selected as parameters running the report

.DESCRIPTION
    Queries the ConfigMgr to get information about applications, devices and software updates. Runs a query towards AD to find information about last computer password change.
    These information can be included or excluded using the parameters. All the information gathered from SCCM and AD will then ultamately be sent to the recipients specified in the mail section.

.PARAMETER runAppScan
    Scan SCCM for applications to find both used an unused applications

.PARAMETER runClientScan
    Scan SCCM Computer Clients to find out how many inactive clients there are

.PARAMETER runPatchCompScan
    SCCM 3 Month Patch Compliance

.PARAMETER runRebootCount
     Find computers that have not rebooted within the last X days

.PARAMETER runOSCount
    AD Count number of unique Operating Systems in OU specified in $ADSearchBase and  Count number of computers within different Operating Systems

.PARAMETER runComputerPasswordCount
    #Find info about when computers last changed Computer password

.PARAMETER runComputerPasswordCount
    #Create the report with everything included

.EXAMPLE
    PCManagementReport -runAppScan $true -runPatchCompScan $true
    Creates a report including SCCM Application Scan and Path Compliance Graph

.EXAMPLE
    PCManagementReport -runEverything $true
    Creates a report including everything

.NOTES
    Cmdlet name:      PCManagementReport
    Author:           Mikael Blomqvist
    DateCreated:      2017-04-19

#>


[CmdletBinding(SupportsShouldProcess = $True)]
param
(
    [Parameter(Mandatory = $False, HelpMessage = "Scan ConfigMgr Applications (True/False)")] 
    [boolean]$runAppScan = $false,
    [Parameter(Mandatory = $False, HelpMessage = "Scan ConfigMgr Devices (True/False)")] 
    [boolean]$runClientScan = $false,
    [Parameter(Mandatory = $False, HelpMessage = "Create Path Compliance Scan (True/False)")] 
    [boolean]$runPatchCompScan = $true,
    [Parameter(Mandatory = $False, HelpMessage = "Calculate Computer Reboot numbers (True/False)")]
    [boolean]$runRebootCount = $false,
    [Parameter(Mandatory = $False, HelpMessage = "Count number of unique Operating Systems and the number of Computers within each Operation Systems (True/False)")]  
    [boolean]$runOSCount = $false,
    [Parameter(Mandatory = $False, HelpMessage = "Calculate Computer password change numbers (True/False)")] 
    [boolean]$runComputerPasswordCount = $false,
    [Parameter(Mandatory = $False, HelpMessage = "Run everything (True/False)")]
    [boolean]$runEverything = $false
)


#Load the ActiveDirectory Module

$m = Get-Module -List ActiveDirectory
if (!$m) {
    Write-Host "AD Powershell Module is not installed can not continue."
    Exit
}

else {
    Import-Module ActiveDirectory
}

$CMSiteServerFQDN = 'siteserver.domain.com' #Configuration Manager Site Server FQDN

$CMSiteServer = $CMSiteServerFQDN.ToString().split(".") | Select -First 1

#Load Configuration Manager PowerShell Module

try {
    Import-Module "\\$CMSiteServer\d$\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1" -ErrorAction Stop
}
catch {
    $_.Exception.Message
    $_.Exception.ItemName
    Write-Host "Cannot load the Configuration Manager PowerShell Module from the specified location"
    break #Terminate if the module cannot be loaded
}

###############################################################################
#
#Edit these to the customer specific values and $CMSiteServer above
#
###############################################################################

#Mail
$smtpServer = "smtprelay.domain.com"
$mailinfo_to = "recipient1@domain.com", "recipient2@domain.com" 
$mailinfo_cc = ""
$mailinfo_from = "$env:computername@domain.com"
$logo = "logo.gif"
$Customername = "My Company"
$sendMail = $true

#Active Directory
[string]$ADSearchBase = "OU=ComputersWin10,DC=domain,DC=com" #Win10
[int]$ComputerPasswordAgeDays = 90
[int]$RebootTresholdDays = 5

#SCCM
$Collection = "All Windows 10" #Collection in SCCM which should be used as basis for the device count
$patchNumberOfMonth = 3 #Number of month contained in the patch compliance overview

#Graphic
$CorporateColor = "#780000"
$BackGroundColor = "#505A55"

#Misc
$ReportTitle = "System Report"
$printToConsole = $true #Write Output to console

##############################################################################
#
#Variables which should not be edited
#
##############################################################################

$File_ExpSuperseededApps = "$PSScriptRoot\ExpiredSuperseededApps.csv"
$File_CompNotBooted = "$PSScriptRoot\NotRebootedComputers.csv"

[string]$Attachments = "$OutFile"

#Mail
$OutFile = "$PSScriptRoot\StatusReport.html"
$subject = "$Customername : Status Report"

#AD
$OUName = (Get-ADOrganizationalUnit -Filter * -Properties CanonicalName -SearchBase $ADSearchBase -SearchScope Base).Name

#SCCM
$SiteCode = Get-PSDrive -PSProvider CMSITE #Configuration Manager Site Code
$SCCMNameSpace = "root\sms\site_$SiteCode"


$head = @"
<style>
H1{color: #000000}
H2{color: #FFFFFF}
H3{color: #000000}
p{color: $CorporateColor; font-size:70%}
TABLE {margin-left:50px;background-color: #FFFFFF;}
TR {padding: 0px}
TH {padding: 5px;background-color: $CorporateColor}
TD {padding: 15px;text-align: Center}
BODY {font-family: verdana;background-color: $BackGroundColor}
</style>
<Title>$ReportTitle</Title>
"@


#Build ADSummary table
$tbl_ADSummary = New-Object System.Data.DataTable ADSummary
$col_ADHeading = New-Object System.Data.DataColumn ADHeading, ([string])
$col_ADData = New-Object System.Data.DataColumn ADData, ([string])
$tbl_ADSummary.columns.Add($col_ADHeading)
$tbl_ADSummary.columns.Add($col_ADData)


#Build AppsSummary table
$tbl_AppsSummary = New-Object System.Data.DataTable AppsSummary
$col_AppsHeading = New-Object System.Data.DataColumn AppsHeading, ([string])
$col_AppsData = New-Object System.Data.DataColumn AppsData, ([string])
$tbl_AppsSummary.columns.Add($col_AppsHeading)
$tbl_AppsSummary.columns.Add($col_AppsData)


#Build DeviceSummary table
$tbl_DeviceSummary = New-Object System.Data.DataTable DeviceSummary
$col_DeviceHeading = New-Object System.Data.DataColumn DeviceHeading, ([string])
$col_DeviceData = New-Object System.Data.DataColumn DeviceData, ([string])
$tbl_DeviceSummary.columns.Add($col_DeviceHeading)
$tbl_DeviceSummary.columns.Add($col_DeviceData)

#Build OS Table
$tbl_ostable = New-Object System.Data.DataTable OS
$Col_OSName = New-Object System.Data.DataColumn OSName, ([string])
$Col_OSCount = New-Object System.Data.DataColumn OSCount, ([string])
$tbl_ostable.Columns.Add($Col_OSName)
$tbl_ostable.Columns.Add($Col_OSCount)

#create patch table and columns
$tbl_Compliance = New-Object System.Data.DataTable Patches
$Col_month = New-Object System.Data.DataColumn Month, ([string])
$Col_compliant = New-Object System.Data.DataColumn Compliant, ([decimal])
$Col_noncompliant = New-Object System.Data.DataColumn NonCompliant, ([decimal])
$Col_unknown = New-Object System.Data.DataColumn Unknown, ([decimal])
$tbl_Compliance.columns.Add($Col_month)
$tbl_Compliance.columns.Add($Col_compliant)
$tbl_Compliance.columns.Add($Col_noncompliant)
$tbl_Compliance.columns.Add($Col_unknown)

#Build PieChart for Last Computer PW Change
$tbl_piechart = New-Object System.Data.DataTable PWChange
$Col_category = New-Object System.Data.DataColumn Category, ([string])
$Col_amount = New-Object System.Data.DataColumn Amount, ([string])
$tbl_piechart.columns.Add($Col_category)
$tbl_piechart.columns.Add($Col_amount)

#Function to add rows to table
Function AddTo-Table {
    $tableName = $args[0]
    $tableHeading = $args[1]
    $tableHeadingValue = $args[2]
    $tableData = $args[3]
    $tableDataValue = $args[4]

    $row = $tableName.NewRow(); $row.$tableHeading = $tableHeadingValue; $row.$tableData = $tableDataValue
    $tableName.Rows.Add($row)

    If ($printToConsole) { Write-Host $tableHeadingValue ": " $tableDataValue }
}

#Function to add attachments to Sting that can be added to the mail 
Function Add-Attachment {

    [string]$file = $args[0]

    $Attachments = $Attachments + ',' + $file

    return $Attachments
}

#Create a Pie Chart with the supplied information
Function CreateChart {

    $datasource = $args[0]
    $title = $args[1]
    $ChartType = $args[2]
    $xValue = $args[3]
    $yValue = $args[4]
    $imagePath = $args[5]
    $imagewidth = $args[6]
    $imageheight = $args[7]
    $displayedinLegend = $args[8]


    [void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms.DataVisualization")
    $scriptpath = $PSScriptRoot
 
    # chart object
    $chart1 = New-object System.Windows.Forms.DataVisualization.Charting.Chart
    $chart1.Width = $imagewidth
    $chart1.Height = $imageheight
    $chart1.BackColor = [System.Drawing.Color]::White
 
    # title 
    [void]$chart1.Titles.Add("$title")
    $chart1.Titles[0].Font = "Verdana,14pt"
    $chart1.Titles[0].Alignment = "topCenter"

    $chart1.Titles[0].ForeColor = $CorporateColor
 
    # chart area 
    $chartarea = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea
    $chartarea.Name = "ChartArea1"
    $chartarea.AxisY.MajorGrid.Enabled = $false
    $chartarea.AxisX.MajorGrid.Enabled = $false
    $chart1.ChartAreas.Add($chartarea)
 
    # legend 
    $legend = New-Object system.Windows.Forms.DataVisualization.Charting.Legend
    $legend.name = "Legend1"
    $chart1.Legends.Add($legend)

    # data series
    [void]$chart1.Series.Add("PWWithin")
    $chart1.Series["PWWithin"].ChartType = "$ChartType"
    $chart1.Series["PWWithin"].BorderWidth = 10
    $chart1.Series["PWWithin"].IsVisibleInLegend = $displayedinLegend
    $chart1.Series["PWWithin"].chartarea = "ChartArea1"
    $chart1.Series["PWWithin"].IsValueShownAsLabel = $true
   

    $datasource | ForEach-Object { $chart1.Series["PWWithin"].Points.addxy( $_.$xValue , $_.$yValue) }
 
    # save chart
    $chart1.SaveImage($imagePath, "png")
}

#Function to Create a stacked patch compliance graph showing the past software update groups
Function CreateComplianceChart {

    $datasource = $args[0]

    $Pic_PatchComp = "PatchCompliance_Stacked100.png"
    $Path_PatchComp = "$PSScriptRoot\$Pic_PatchComp"


    [void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms.DataVisualization")
    $scriptpath = $PSScriptRoot
 
    # chart object
    $chart1 = New-object System.Windows.Forms.DataVisualization.Charting.Chart
    $chart1.Width = 800
    $chart1.Height = 600
    $chart1.BackColor = [System.Drawing.Color]::White
 
    # title 
    [void]$chart1.Titles.Add("Past $patchNumberOfMonth Month Patch Compliance Development")
    $chart1.Titles[0].Font = "Verdana,20pt"
    $chart1.Titles[0].Alignment = "topCenter"

    $chart1.Titles[0].ForeColor = $CorporateColor
 
    # chart area 
    $chartarea = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea
    $chartarea.Name = "ChartArea1"
    $chartarea.AxisY.Title = "Compliance %"
    $chartarea.AxisX.Title = "Month"
    $chartarea.AxisY.Interval = 10
    $chartarea.AxisX.Interval = 1
    $chart1.ChartAreas.Add($chartarea)
 
    # legend 
    $legend = New-Object system.Windows.Forms.DataVisualization.Charting.Legend
    $legend.name = "Legend1"
    $chart1.Legends.Add($legend)
 

    # data series
    [void]$chart1.Series.Add("Compliant")
    $chart1.Series["Compliant"].ChartType = "StackedColumn100"
    $chart1.Series["Compliant"].BorderWidth = 10
    $chart1.Series["Compliant"].IsVisibleInLegend = $true
    $chart1.Series["Compliant"].chartarea = "ChartArea1"
    $chart1.Series["Compliant"].Legend = "Legend1"
    $chart1.Series["Compliant"].color = "#2ECC71"
    $chart1.Series["Compliant"].IsValueShownAsLabel = $true
    $datasource | ForEach-Object { $chart1.Series["Compliant"].Points.addxy( $_.Month , $_.Compliant) }
 

 
    # data series
    [void]$chart1.Series.Add("NonCompliant")
    $chart1.Series["NonCompliant"].ChartType = "StackedColumn100"
    $chart1.Series["NonCompliant"].IsVisibleInLegend = $true
    $chart1.Series["NonCompliant"].BorderWidth = 10
    $chart1.Series["NonCompliant"].chartarea = "ChartArea1"
    $chart1.Series["NonCompliant"].Legend = "Legend1"
    $chart1.Series["NonCompliant"].color = "#E74C3C"
    $chart1.Series["NonCompliant"].IsValueShownAsLabel = $true
    $datasource | ForEach-Object { $chart1.Series["NonCompliant"].Points.addxy( $_.Month , $_.NonCompliant) }


    # data series
    [void]$chart1.Series.Add("Unknown")
    $chart1.Series["Unknown"].ChartType = "StackedColumn100"
    $chart1.Series["Unknown"].IsVisibleInLegend = $true
    $chart1.Series["Unknown"].BorderWidth = 10
    $chart1.Series["Unknown"].chartarea = "ChartArea1"
    $chart1.Series["Unknown"].Legend = "Legend1"
    $chart1.Series["Unknown"].color = "#CACFD2"
    $chart1.Series["Unknown"].IsValueShownAsLabel = $true
    $datasource | ForEach-Object { $chart1.Series["Unknown"].Points.addxy( $_.Month , $_.Unknown) }
 
    # save chart
    $chart1.SaveImage("$PSScriptRoot\PatchCompliance_Stacked100.png", "png")
}


# This function is used to send the email containing the report and attachments. 
# The reason this one is used instead of Send-MailMessage is that .NET allows images to be embedded in the email using the cid tag

Function SendMail {

    $mailbody = $args[0]
    $mailattachments = $args[1]

    $SmtpClient = New-Object System.Net.Mail.SmtpClient($smtpServer)
    $MailMessage = New-object System.Net.Mail.MailMessage($mailinfo_from, $mailinfo_to, $Subject, $mailbody)
    $MailMessage.IsBodyHtml = $true


    $trimlenght = $PSScriptRoot.Length + 1 #have to add 1 because of the extra / in the path

    Foreach ($attachment in $mailattachments) {

        $attachmentID = $attachment.SubString($trimlenght) #This is to use only the actualy filename as ID
        $obj_attachment = New-Object System.Net.Mail.Attachment($attachment)
        $obj_attachment.ContentID = $attachmentID
        $MailMessage.Attachments.Add($obj_attachment)

    }

    $SmtpClient.Send($MailMessage)

}

$Attachments = Add-Attachment "$PSScriptRoot\$logo"


#Get all Computers within the specified SearchBase excluding Servers
$Computers = Get-ADComputer -SearchBase $ADSearchBase -filter { (OperatingSystem -notlike "*Server*") } -properties Name, DistinguishedName, OperatingSystem, OperatingSystemServicePack, passwordLastSet, LastLogonDate, Description

AddTo-Table $tbl_ADSummary "ADHeading" "Number of computers in $OUName" "ADData" $Computers.Count

If ($printToConsole) {
    Write-Host "Overview:"
    Write-Host "--------------------"
    Write-Host "Total number of computers: "$Computers.Count
}



#Count number of unique OS
If ($runOSCount -or $runEverything) {
    #Change variable to UComputers and work with this
    $UComputers = $Computers | select-object Name, OperatingSystem, OperatingSystemServicePack, passwordLastSet, LastLogonDate, DistinguishedName, CountryOUADvariable

    $UniqueOS = $UComputers | Select-Object OperatingSystem | Sort OperatingSystem -Descending | Get-Unique -Asstring

    #Count number of unique OS
    $NumberofUniqueOS = 0
    foreach ($uos in $UniqueOS) { $NumberofUniqueOS++ }

    #AddTo-Table $tbl_ADSummary "ADHeading" "Number of different Operating Systems" "ADData" $NumberofUniqueOS


    #Meassure/Count how many computer object of each OS type

    foreach ($os in $UComputers | Select-Object OperatingSystem | Sort OperatingSystem -Descending | Get-Unique -Asstring) {
        $count = ($UComputers | where { $_.OperatingSystem.ToString() -eq $os.OperatingSystem } | measure).Count
        AddTo-Table $tbl_ostable "OSName" ($os.OperatingSystem.ToString()) "OSCount" $count
        $count = 0
    }
    #CreateOSChart $tbl_ostable
    CreateChart $tbl_ostable "Operating Systems" "Pie" "OSName" "OSCount" "$PSScriptRoot\OS_Pie.png" "400" "200" $true

    $Attachments = Add-Attachment "$PSScriptRoot\OS_Pie.png"

}

CD $SiteCode':'


#Finding Computers that are within and outside the treshold of when Computer password was last set
If ($runComputerPasswordCount -or $runEverything) {

    $ComputerStaleDate = (Get-Date).AddDays(-$ComputerPasswordAgeDays)

    $PWWithin = $Computers | Where { $_.passwordLastSet -ge $ComputerStaleDate }
    $PWOutside = $Computers | Where { $_.passwordLastSet -le $ComputerStaleDate }

    AddTo-Table $tbl_piechart "Category" "Within Treshold" "Amount" $PWWithin.Count
    AddTo-Table $tbl_piechart "Category" "Outside Treshhold" "Amount" $PWOutside.Count

    If ($printToConsole) {
        Write-Host "Computer Password:"
        Write-Host "--------------------"
        Write-Host "Within Treshold: "$PWWithin.Count
        Write-Host "Outside Treshhold: "$PWOutside.Count
    }

    #CreateComputerPWChart $tbl_piechart

    CreateChart $tbl_piechart "Computer Password" "Pie" "Category" "Amount" "$PSScriptRoot\ComputerPW_Pie.png" "400" "200" $true

    $ComputersinSCCM = $PWOutside | Where-Object { $_ -ne $null } | Foreach-Object { Get-CMDevice -Name $_.Name }
    AddTo-Table $tbl_ADSummary "ADHeading" "In SCCM but havent changed PW within $ComputerPasswordAgeDays days" "ADData" $ComputersinSCCM.Count

    $Attachments = Add-Attachment "$PSScriptRoot\ComputerPW_Pie.png"
}

#Count number of computers that havent been rebooted within the number of days specified in $RebootTresholdDays
If ($runRebootCount -or $runEverything) {
    $OUCanonicalName = (Get-ADOrganizationalUnit -Filter * -Properties CanonicalName -SearchBase "$ADSearchBase" -SearchScope Base).CanonicalName
    
    $computersNotRebooted = (Get-WmiObject -namespace $SCCMNameSpace -computer $CMSiteServerFQDN -query "SELECT DISTINCT SMS_R_System.Name, SMS_G_System_OPERATING_SYSTEM.LastBootUpTime, SMS_G_System_OPERATING_SYSTEM.Caption
    FROM SMS_R_System INNER JOIN SMS_G_System_OPERATING_SYSTEM ON SMS_G_System_OPERATING_SYSTEM.ResourceID = SMS_R_System.ResourceId
    WHERE (DateDiff(day, SMS_G_System_OPERATING_SYSTEM.LastBootUpTime, GetDate()) >$RebootTresholdDays) AND SMS_R_System.SystemOUName = '$OUCanonicalName'").SMS_R_System | Select Name

    AddTo-Table $tbl_ADSummary "ADHeading" "Not rebooted within the last $RebootTresholdDays days" "ADData" $computersNotRebooted.Count

    If ($printToConsole) {
        Write-Host "Computer Reboot:"
        Write-Host "Computers not rebooted with $RebootTresholdDays : " $computersNotRebooted.Count

    }

}

#Get the last 3 SoftwareUpdate Groups
If ($runPatchCompScan -or $runEverything) {

    
    $SUGs = Get-CMSoftwareUpdateGroup | Sort-Object { $_.datecreated } -Descending | Select -First $patchNumberOfMonth

    If ($printToConsole) {
        Write-Host "Software Update Groups:"
        Write-Host "-----------------------"
    }

    Foreach ($sug in $SUGs) {

        $month = $sug.LocalizedDisplayName
        $compliant = [math]::Round((($sug.NumCompliant / ($sug.NumTotal)) * 100), 2)
        $noncompliant = [math]::Round((($sug.NumNonCompliant / ($sug.NumTotal)) * 100), 2)
        $unknown = [math]::Round((($sug.NumUnknown / $sug.NumTotal) * 100), 2)

        If ($printToConsole) {
            Write-Host "Month: $month"
            Write-Host "-Compliant: $Compliant"
            Write-Host "-NonCompliant: $noncompliant"
            Write-Host "-Unknown: $unknown"
        }
        

        $row = $tbl_Compliance.NewRow(); $row.Month = $month; $row.Compliant = $compliant; $row.NonCompliant = $noncompliant; $row.Unknown = $unknown
        $tbl_Compliance.Rows.Add($row)

    }


    CreateComplianceChart $tbl_Compliance

    $Attachments = Add-Attachment "$PSScriptRoot\PatchCompliance_Stacked100.png"


}

#Count number of Applications in SCCM
If ($runAppScan -or $runEverything) {
    $AllApps = Get-CMApplication
    $DeployedSuperseeded = $AllApps | Where-Object { <#($_.IsDeployed -eq $true) -and #>($_.IsSuperseded -eq $true) } 
    $DeployedExpired = $AllApps | Where-Object { <#($_.IsDeployed -eq $true) -and#> ($_.IsExpired -eq $true) }
    $DeployedWorking = $AllApps | Where-Object { ($_.IsDeployed -eq $true) -and ($_.IsExpired -eq $false) -and ($_.IsSuperseded -eq $false) }
    $NotDeployed = $AllApps | Where-Object { ($_.IsDeployed -eq $false) }

  
    #AddTo-Table $tbl_AppsSummary "AppsHeading" "Total number of Applications in SCCM" "AppsData" $AllApps.Count
    AddTo-Table $tbl_AppsSummary "AppsHeading" "Superseeded Applications" "AppsData" $DeployedSuperseeded.Count
    AddTo-Table $tbl_AppsSummary "AppsHeading" "Expired Applications" "AppsData" $DeployedExpired.Count
    AddTo-Table $tbl_AppsSummary "AppsHeading" "Active Applications" "AppsData" $DeployedWorking.Count
    AddTo-Table $tbl_AppsSummary "AppsHeading" "Applications without deployment(s)" "AppsData" $NotDeployed.Count
    
    CreateChart $tbl_AppsSummary "All Applications" "Pie" "AppsHeading" "AppsData" "$PSScriptRoot\Applications_Pie.png" "400" "200" $true

    $Attachments = Add-Attachment "$PSScriptRoot\Applications_Pie.png"

    If ($printToConsole) {
        Write-Host "Applications:"
        Write-Host "--------------------"
        Write-Host "Superseeded Applications: "$DeployedSuperseeded.Count
        Write-Host "Expired Applications: "$DeployedExpired.Count
        Write-Host "Active Applications: "$DeployedWorking.Count
        Write-Host "Applications without deployment(s): "$NotDeployed.Count
    }

}

#Count devices in SCCM
If ($runClientScan -or $runEverything) {

    $AllDevicesinSCCM = Get-CMDevice -CollectionName "$Collection"
    $AciveDevices = $AllDevicesinSCCM | Where-Object { ($_.ClientActiveStatus -eq 1) }
    $InaciveDevices = $AllDevicesinSCCM | Where-Object { ($_.ClientActiveStatus -eq 0) }
    $ObsoleteDevices = $AllDevicesinSCCM | Where-Object { ($_.IsObsolete -eq $true) }
    $NoSCCMClient = $AllDevicesinSCCM | Where-Object { ($_.IsClient -eq $false) }

    AddTo-Table $tbl_DeviceSummary "DeviceHeading" "Active Devices" "DeviceData" $AciveDevices.Count
    AddTo-Table $tbl_DeviceSummary "DeviceHeading" "Obsolete devices" "DeviceData" $ObsoleteDevices.Count
    AddTo-Table $tbl_DeviceSummary "DeviceHeading" "Inactive Devices" "DeviceData" $InaciveDevices.Count
    AddTo-Table $tbl_DeviceSummary "DeviceHeading" "Devices without Client" "DeviceData" $NoSCCMClient.Count

    CreateChart $tbl_DeviceSummary "Devices in $Collection" "Pie" "DeviceHeading" "DeviceData" "$PSScriptRoot\Devices_Pie.png" "400" "200" $true

    $Attachments = Add-Attachment "$PSScriptRoot\Devices_Pie.png"

    If ($printToConsole) {
        Write-Host "Devices:"
        Write-Host "--------------------"
        Write-Host "Active Devices: "$AciveDevices.Count
        Write-Host "Obsolete devices: "$ObsoleteDevices.Count
        Write-Host "Inactive Devices: "$InaciveDevices.Count
        Write-Host "Devices without Client: "$NoSCCMClient.Count
    }
}

CreateChart $tbl_ADSummary "Computer Info" "Bar" "ADHeading" "ADData" "$PSScriptRoot\ComputerInfo_Bar.png" "800" "300" $false

$Attachments = Add-Attachment "$PSScriptRoot\ComputerInfo_Bar.png"


#Convert All tables to HTML
#--------------------------------------------------------

#Convert the ostable to a HTML Fragment that can be inserted into the html document.
$osfragment = $tbl_ostable | Select @{Name = "Operating System"; Expression = { $_.OSName } }, @{Name = "Count"; Expression = { $_.OSCount } } | ConvertTo-Html -Fragment
$adfragment = $tbl_ADSummary | Select ADHeading, ADData | ConvertTo-Html -Fragment
$sccmfragment = $tbl_AppsSummary | Select AppsHeading, AppsData | ConvertTo-Html -Fragment

#Write results depending on parameter set
$footer = "Report ran from {3} initiated by {1}\{2} at {0}" -f (Get-Date), $env:USERDOMAIN, $env:USERNAME, $env:COMPUTERNAME


$fragments = @()

#Insert a graphic header
$fragments += "<Center><table>"
$fragments += "<tr><td><Img src='cid:$logo'></td></tr>"
$fragments += "<tr><td><H1>$Customername - Environment Summary</H1></td></tr>"
$fragments += "<tr><td ><h3>Computers in: $OUName</h3></td></tr>"

#Only show this section if any of the elements in it are choosen to be calculated
If ($runRebootCount -or $runComputerPasswordCount -or $runOSCount -or $runEverything) { 
    $fragments += "<tr><th><H2>Active Directory Summary</H2></th></tr>"

    If ($runRebootCount -or $runEverything) { 
        $fragments += "<tr><td><Img src='cid:ComputerInfo_Bar.png'></td></tr>"

    }

    If ($runComputerPasswordCount -or $runOSCount -or $runEverything) { 
        $fragments += "<tr><td>"
        If ($runOSCount -or $runEverything) {
            $fragments += "<Img src='cid:OS_Pie.png'>"
        }
        If ($runComputerPasswordCount -or $runEverything) {           
            $fragments += "<Img src='cid:ComputerPW_Pie.png'>"
        }
        
        $fragments += "</td></tr>"
    }

}
#Only show the SCCM Summary in the report if either runAppScan or runClientScan is enabled.
If ($runAppScan -or $runClientScan -or $runEverything) {
    $fragments += "<tr><th><H2>SCCM Summary</H2></th></tr>"

   
    $fragments += "<tr><td>"
    If ($runAppScan -or $runEverything) {
        $fragments += "<Img src='cid:Applications_Pie.png'>"
    }
    If ($runClientScan -or $runEverything) {           
        $fragments += "<Img src='cid:Devices_Pie.png'>"
    }
        
    $fragments += "</td></tr>"
  
}

#Only show the Patch Compliance Overview if runPatchCompScan is enabled
If ($runPatchCompScan -or $runEverything) {
    $fragments += "<tr><th><H2>Patch Compliance Overview</H2></th></tr>"
    $fragments += "<tr><td><Img src='cid:PatchCompliance_Stacked100.png'></td></tr>"
}

$fragments += "<tr><td><p>$footer</p></td></tr>"
$fragments += "</table></Center>"


CD c:\


#Write to Files
$DeployedSuperseeded | Select @{Name = "Superseeded Applications"; Expression = { $_.LocalizedDisplayName } } | Out-File $File_ExpSuperseededApps
$DeployedExpired | Select @{Name = "Expired Applications"; Expression = { $_.LocalizedDisplayName } } | Out-File $File_ExpSuperseededApps -Append
$NotDeployed | Select @{Name = "Applications without Deployments"; Expression = { $_.LocalizedDisplayName } } | Out-File $File_ExpSuperseededApps -Append

$computersNotRebooted | Out-File $File_CompNotBooted

#Adding files to attachment array if they have been created.

If ($runRebootCount) {
    $Attachments = Add-Attachment $File_CompNotBooted
}

If ($runAppScan) {
    $Attachments = Add-Attachment $File_ExpSuperseededApps
}

$body = ConvertTo-Html -Head $head -Title $ReportTitle -Body $fragments

#For the html report the cid is removed so the image src path is correct
$report = $fragments -replace "cid:", ""

ConvertTo-Html -Head $head -Title $ReportTitle -Body $report | Out-File $OutFile

$ArrayAttachment = $Attachments.Split(",")

If ($sendMail) {
    sendmail $body $ArrayAttachment
}
Else {
    Write-Host "Send Mail is disabled so no mail were sent."
}

Script to Report on Recent Deployments

This Script can be used to automate reporting on application deployment. With the current settings it gathers deployment summary for the deployments that was created within the last 14 days and sends an email to the defined recipient(s).

All you have to do is to change the variables under #Mail and #Variables to match your domain settings and verifiy the import-module path.

After that you can set up a scheduled task that runs this script with the frequency that you want.

#Mail
$smtpServer = "smtprelay.domain.com"
$mailinfo_from = "server@domain.com"
$mailinfo_to = "initials@domain.com", "initials2@domain.com"
$mailinfo_cc = ""
$subject = "Deployment Summary Report"
$logoURL = "<Img src='https://www.url.com/picture.gif' style='float:left' width='99' height='75' hspace=10>"

#Variables
$ComputerName="sccmserver.domain.com"
$SiteCode = "SiteCode"
$ReportTitle="System Report"
$period = 14
$OutFile = "$PSScriptRoot\DeploymentReport.html"

Import-Module '\\$ComputerName\d$\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'

$head = @"
<style>
TABLE {border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;margin-left:50px}
TH {border-width: 1px;padding: 3px;border-style: solid;border-color: black;background-color: #8c0000;}
TD {border-width: 1px;padding: 3px;border-style: solid;border-color: black;}
.odd  { background-color:#ffffff; }
.even { background-color:#dddddd; }
</style>
<Title>$ReportTitle</Title>
"@

#Computer system
$cs = Get-WmiObject -Class Win32_Computersystem -ComputerName $computername
$csdata = $cs | Select Status,Manufacturer,Model,SystemType,Number*

#Deployment Summary
$timeSpan = (Get-Date).Adddays(-$period)
$GetDeployments = Get-WmiObject -Namespace root\sms\site_$SiteCode -Class SMS_DeploymentSummary -ComputerName $ComputerName | Where-Object {($_.FeatureType -eq "1") -AND ($_.CollectionName -like "*-install (device)") -AND ($_.CollectionName -like "*-install (device)") -AND ($_.ConvertToDateTime($_.CreationTime) -gt $timeSpan)}
$deploymentSummary = $GetDeployments | Select-Object CollectionName,@{Label="CreationTime";Expression={$_.ConvertToDateTime($_.CreationTime)}},@{Label="Deployment";Expression={$_.SoftwareName}},NumberSuccess,NumberInProgress,NumberOther,NumberErrors,FeatureType
cd dd1:
$deploymentSummary=$deploymentSummary | Where-Object {(Get-CMApplication -Name $_.Deployment).IsSuperseded -eq $false}

#Write results depending on parameter set
$footer="Report ran from {3} initiated by {1}\{2}" -f (Get-Date),$env:USERDOMAIN,$env:USERNAME,$env:COMPUTERNAME

#Prepare HTML code
$fragments = @()

#Insert a graphic header
$fragments += "$logoURL<br><br>"
$fragments += $deploymentsummary | ConvertTo-HTML -Fragment -PreContent "<H2>Deployment Summary last $period days</H2>"

Write $fragments | clip
cd c:\
ConvertTo-Html -Head $head -Title $ReportTitle -PreContent ($fragments | out-String) -PostContent "<br><br><font color='red'>Superseeded Deployments are not displayed even tho they have been created within the last $period days</font><br><br><I>$footer</I>" | Out-File $OutFile

$Body = Get-Content $OutFile

#If there are no deployments created within the treshhold dont send a mail
If ($deploymentSummary.Count -notlike 0){
    Send-MailMessage -SmtpServer $smtpServer -To $mailinfo_to -From $mailinfo_from -Subject $subject -Body "$Body" -BodyAsHtml -Attachments $OutFile
}
Get-Item $OutFile | Remove-Item -Force

Script to Report on Software Update Group compliance

This script will collect the status of the last created Software Update Group and send it as a mail to the specified recipient(s).

All you have to do is to change the variables under #Mail and #Variables to match your domain settings.

After that you can set up a scheduled task that runs this script with the frequency that you want.

Import-Module '\\sccmserver\d$\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'

#Mail
$smtpServer = "smtprelay.customer.com"
$mailinfo_from = "server1@customer.com"
$mailinfo_to = "initials@company.com"
$mailinfo_cc = ""
$subject = "Windows 10 Patch Compliance - "

#Variables
$tableheading = "Windows 10 Patch Compliance - "
$logoURL = "<Img src='https://www.url.com/picture.gif' style='float:left' width='99' height='75' hspace=10>"
[string]$SavePath = "c:\temp\Compliance Reports"
$year = (Get-Date).year
$month = Get-Date -Format MM
$SiteCode = "SiteCode"


$ReportTitle = "System Report"
[string]$reportVersion = "1.0"


$head = @"
<style>
TABLE {border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;margin-left:50px}
TH {border-width: 1px;padding: 3px;border-style: solid;border-color: black;background-color: #8c0000;}
TD {border-width: 1px;padding: 3px;border-style: solid;border-color: black;}
.odd  { background-color:#ffffff; }
.even { background-color:#dddddd; }
</style>
<Title>$ReportTitle</Title>
"@


#Access the SCCM Site
Set-Location $SiteCode:

#Finding the latest created Software Update Group

$SUG = Get-CMSoftwareUpdateGroup | Sort-Object { $_.datecreated } -Descending | Select -First 1

$updategroupname = $SUG.LocalizedDisplayName

#Getting all updates on that Software update Group

$Updates = (Get-CMSoftwareUpdate -UpdateGroupName $updategroupname) | Select-Object Vendor, ArticleID, BulletinID, @{E = { $_.LocalizedDisplayName }; L = "Titel" }, @{E = { $_.NumPresent }; L = "Installed" }, @{E = { $_.NumMissing }; L = "Required" }, @{E = { $_.NumNotApplicable }; L = "Not Required" } , @{E = { $_.NumUnknown }; L = "Unknown" }, @{E = { $_.NumTotal }; L = "Total" }, @{E = { [math]::Round((($_.NumPresent / ($_.NumPresent + $_.NumMissing)) * 100), 2) }; L = "% Compliant" }, @{E = { [math]::Round((($_.NumMissing / ($_.NumPresent + $_.NumMissing)) * 100), 2) }; L = "% Not Compliant" }, @{E = { [math]::Round((($_.NumUnknown / $_.NumTotal) * 100), 2) }; L = "% Unknown" }

#Write results depending on parameter set
$footer = "Report ran from {3} initiated by {1}\{2}" -f (Get-Date), $env:USERDOMAIN, $env:USERNAME, $env:COMPUTERNAME

#Prepare HTML code
$fragments = @()

#Insert a graphic header
$fragments += "$logoURL<br><br>"

$fragments += $Updates | ConvertTo-HTML -Fragment -PreContent "<H2>$tableheading $updategroupname</H2>"


write $fragments | clip
$outFile = ConvertTo-Html -Head $head -Title $ReportTitle -PreContent ($fragments | out-String) -PostContent "<br><I>$footer</I>"


#Checks savepath and export file.
IF ((test-path $SavePath) -eq $False) { md $SavePath }
$ExportFile = "$SavePath\Win 10 Compliance-$updategroupname.csv"
IF (Test-Path $ExportFile) { Remove-Item $ExportFile }


$Updates | export-csv $ExportFile -Append -Force -NoTypeInformation

$body = "" + $OutFile

Send-MailMessage -SmtpServer $smtpServer -From $mailinfo_from -To $mailinfo_to -Subject "$subject $updategroupname" -Body $body -BodyAsHtml -Attachments $ExportFile

Create OU Based Collections

I recently set up a whole new SCCM 2012 environment and we needed to create collections for a lot of OUs containing the computers.The script below will run through an OU structure and create device collections for each OU and sub OU’s.

To run the script open your SCCM console and press the drop down menu next to the home button and choose “Connect via Windows Powershell”.

This will establish a connection to the SCCM and allow you to use some special CM powershell functions which is utilized in this script.

To avoid warnings about the script not  being digitally signed run this command prior to executing the script it self.

Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass

Now you can run the script.

It takes 1 mandatory argument and 3 optional which if not specified will default to the values they have in the script.

The mandatory argument is the SearchBase which is the top level OU from which you want to create the OU based Collections. The Optional arguments are LimitingCollection, SearchScope and RefreshType.

To run the script in its most simple form:

New-OUDeviceCollection.ps1 -SearchBase “OU=Computers,OU=Company,DC=OMG,DC=local”

This will create a collection for each of the OU’s under omg.local\Company\Computers – excluding the Computers container it self.

New-OUDeviceCollection.ps1 -SearchBase “OU=Computers,OU=Company,DC=OMG,DC=local” -LimitingCollection “All Windows 10 Computers” -SearchScope Subtree -RefreshType 2

This will create a collection for each of the OU’s under omg.local\Company\Computers – including the Computers container it self. It will use “All Windows 10 Computers” as the limiting Collection and it will use periodic update.

# Name: New-OUDeviceCollection
# Arguments: 4 (1 Mandatory, 3 Optional)
#   1. SearchBase (String, Mandatory): The top level OU from where you want to create OU Collections. The DistinguisedName attribute of the OU from AD is used.
#   2. LimitingCollection (String, Optional): The Collection to limit the new collections to. Default: All Desktop and Server Clients
#   3. SearchScope (String, Optional): Subtree, OneLevel or Base. Default: OneLevel
#   4. RefreshType (String, Optional): 1 (Manual),2 (Periodic), 4(CONSTANT_UPDATE) - Default: 4
#
# Description: This script will run through an OU and create device collections for each OU and sub OU's 
#              depending on what you specify in SearchScope.


param(
[string]$SearchBase,
[string]$LimitingCollection = 'All Desktop and Server Clients',
[string]$SearchScope = 'OneLevel',
[string]$RefreshType = '4'
)


$OUS = Get-ADOrganizationalUnit -SearchBase $SearchBase -SearchScope $SearchScope -Filter * -Properties Canonicalname
foreach ($OU in $OUS) 
 {
    $OUName=$OU.Name
    $Canonical=$OU.CanonicalName
    New-CMDeviceCollection -Name "$OUName" -LimitingCollectionName $LimitingCollection -RefreshType $RefreshType
    Add-CMDeviceCollectionQueryMembershipRule -CollectionName "$OUName" -QueryExpression "select SMS_R_SYSTEM.ResourceID, SMS_R_SYSTEM.ResourceType,
    SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,SMS_R_SYSTEM.ResourceDomainORWorkgroup,SMS_R_SYSTEM.Client from SMS_R_System where SMS_R_System.SystemOUName
   = '$Canonical'" -RuleName "$OUName OU"
 }

Find all files with specific content

This Script will search c:\temp to find all files called test.txt that contains the string “server2”.

Get-ChildItem -Path c:\temp -File -Recurse -include test.txt | Select-String -pattern "server2" | Group path | Select Name

 

Replace Text in a File

This script will replace the occurence of “server1” with “server2” in all files called config.ini placed under c:\temp

Get-ChildItem -Path c:\temp -File -Recurse -include config.ini | %{(Get-Content $_) -replace "server1","server2" | Set-Content $_.fullname}