Commit 298e59f5 authored by Saleh Raghib's avatar Saleh Raghib
Browse files

Added app install status to Slack/Teams message in smsts-post-action script....

Added app install status to Slack/Teams message in smsts-post-action script. Testing with BETA first before adding it to live script.
parent 4423ee85
# Post Action Script for running post-Task Sequence tasks
# Originally developed by tewebb. Modded and converted to PS by mseng3
# Usage example #1:
# powershell.exe -ExecutionPolicy Bypass -file "\\engr-wintools.ad.uillinois.edu\packagedsoftware$\ushar\scripts\SMSTSPostAction.ps1" -email "netid@illinois.edu"
# Usage example #2:
# powershell.exe -ExecutionPolicy Bypass -file "\\engr-wintools.ad.uillinois.edu\packagedsoftware$\ushar\scripts\SMSTSPostAction.ps1" -SlackMsg "%_SMSTSMachineName% (model: %EngrIT_Model%) finished imaging with TS: %_SMSTSPackageName%." -SlackChannel "yourchannel" -SlackName "YourBot" -SlackIcon "http://helptools.engrit.illinois.edu/images/block_i_70x70.png"
# Parameters
param(
# If specified, will run the SCCM client's "Machine Policy Retrieval & Evaluation Cycle". Omit to like, not do that.
[switch]$SCCMPolicyCheck,
# Specify an address to send a completion email to. Omit to row row fight the powah.
[string]$Email='false',
# Specify a message to send to Slack upon completion. Omit to make puppies cry.
# Message will be appended with info that can't be gathered before hand (timestamp and actual computername)
# In the future it might be nicer to pass all the info as a single delimited string?
[string]$SlackMsg='false',
# The Slack channel to send the msg to. Omit to use default.
[string]$SlackChannel,
# The name under which the Incoming Webhook bot will post the message. Omit to use default.
[string]$SlackName,
# The icon that will be used as the bot's avatar (a URL). Omit to use default.
[string]$SlackIcon,
# The webhook to use for sending Slack messages. If you haven't, set up your own instance of the "Incoming Webhook" integration module on Slack.
[string]$SlackWebhook,
# Specify a message to send to Teams upon completion. Omit to take candy away from babies.
# Message will be appended with info that can't be gathered before hand (timestamp and actual computername)
# In the future it might be nicer to pass all the info as a single delimited string?
[string]$TeamsMsg='false',
# The name under which the Incoming Webhook bot will post the message. Omit to use default.
[string]$TeamsName,
# The webhook to use for sending Teams messages.
[string]$TeamsWebhook,
# The value of %_SMSTSMachineName% (or %EngrIT_Computername%)
# Probably can't use these TS-given environment variables directly since this script is running after the task sequence ends, so we have to pass them as arguments
[string]$ObjectName,
# The value of %_SMSTSPackageName%
[string]$TS,
# Timestamp of imaging start time (for calculating time spent imaging)
# in format: YYYY-MM-DD HH:mm:ss
[string]$StartTime='unknown',
# Set to a workgroup name to run UnjoinDomain.ps1, which unjoins the domain and joins the specified workgroup
[string]$UnjoinDomain='false',
# Required for unjoining
[string]$UnjoinUser,
# Required for unjoining
[string]$UnjoinPass,
# Set cmtrace.exe as default log viewer
[switch]$CMTraceForLogs,
# Skip running Windows Updates
[switch]$SkipWU,
# Run the High Performance Power Plan script as a post-action
[switch]$HighPower,
# Post list of successful and failed app installs to Slack and Teams
[switch]$AppInstallStatusMsg
)
function log($msg) {
$timestamp = get-date -uformat "%Y-%m-%d %T"
$msg = "[$timestamp] $msg"
write-output $msg
}
# Modified from https://github.com/RichPrescott/Functions/blob/master/SCCM/Get-CMLog.ps1
function Get-CMLog
{
param(
[Parameter(Mandatory=$true,
Position=0,
ValueFromPipelineByPropertyName=$true)]
[Alias("FullName")]
$Path,
[String[]]$Patterns
)
PROCESS
{
ForEach ($File in $Path){
$FileName = Split-Path -Path $File -Leaf
$logLines = @()
#Write-Host $Patterns
ForEach ($pattern in $Patterns)
{
$logLines += (Select-String -Path $File -Pattern $pattern | Foreach {$_.Line})
}
ForEach ($line in $logLines) {
If ($line.substring(0,7) -eq '<![LOG[') {
$line -match '\<\!\[LOG\[(?<Message>.*)?\]LOG\]\!\>\<time=\"(?<Time>.+)(?<TZAdjust>[+|-])(?<TZOffset>\d{2,3})\"\s+date=\"(?<Date>.+)?\"\s+component=\"(?<Component>.+)?\"\s+context="(?<Context>.*)?\"\s+type=\"(?<Type>\d)?\"\s+thread=\"(?<TID>\d+)?\"\s+file=\"(?<Reference>.+)?\"\>' | Out-Null
}
Else {
$line -match '(?<Message>.*)? \$\$\<(?<Component>.*)?\>\<(?<Date>.+) (?<Time>.+)(?<TZAdjust>[+|-])(?<TZOffset>\d{2,3})?\>\<thread=(?<TID>.*)\>' | Out-Null
}
[pscustomobject]@{
#UTCTime = [datetime]::ParseExact($("$($matches.date) $($matches.time)$($matches.TZAdjust)$($matches.TZOffset/60)"),"MM-dd-yyyy HH:mm:ss.fffz", $null, "AdjustToUniversal")
LocalTime = [datetime]::ParseExact($("$($matches.date) $($matches.time)"),"MM-dd-yyyy HH:mm:ss.fff", $null)
FileName = $FileName
Component = $matches.component
Context = $matches.context
Type = $matches.type
TID = $matches.TID
Reference = $matches.reference
Message = $matches.message
}
}
}
}
}
log "PSVersionTable:"
log $PSVersionTable
# For convenience
$scripts = "c:\engrit\scripts"
$engritLogPath = "c:\engrit\logs"
$smstsLogPath = "c:\windows\ccm\logs"
#Set-PSDebug -Trace 1
# Sets the cmd window title as a warning because it's easier than printing it out
$windowtitle = 'Do not restart this computer. Updates are being installed. It will restart automatically when completed.'
log "Setting window title to $windowtitle...".
$host.ui.RawUI.WindowTitle = $windowtitle
# Adds current date to registry key for tracking purposes
$date = get-date -uformat "%Y-%m-%d"
log "Writing image date ($date) to registry (HKLM:\System\Image\ImageDate) for tracking purposes..."
reg add HKLM\System\Image /v ImageDate /t REG_SZ /d "$date" /f
# Modernized reg key addition
$regKey = "HKLM:\System\EngrIT"
$regValueName = "ImageDate"
$regValueType = "String" # i.e. REG_SZ
$regValueData = get-date -uformat "%Y-%m-%d_%H-%M-%S"
log "Writing image date ($regValueData) to registry ($regKey\$regValueName) for tracking purposes..."
New-Item -Path $regKey # If exists, will fail and not overwrite anything, which is what we want
New-ItemProperty -Path $regKey -Name $regValueName -PropertyType $regValueType -Value $regValueData -Force # If exists, will overwrite
# updates GPOs
# If a user logs in before the gpupdate finishes, and the gpupdate requires a relog/restart, this script can hang, waiting for the user to type y or n, even though the cmd window is invisible. Piping "echo n" here solves this.
log "Running gpupdate /force..."
echo n | gpupdate /force
if($SCCMPolicyCheck) {
log "Polling for SCCM client policy..."
# https://www.systemcenterdudes.com/configuration-manager-2012-client-command-list/
# https://blogs.technet.microsoft.com/charlesa_us/2015/03/07/triggering-configmgr-client-actions-with-wmic-without-pesky-right-click-tools/
# https://rid500.wordpress.com/2017/07/23/sccm-refresh-machine-policy-retrieval-evaluation-cycle-via-wmi/
# https://www.asquaredozen.com/2018/06/14/triggering-configmgr-client-actions-from-a-task-sequence/#comment-107
# The point of this is to expedite policy retrieval so that apps show up in Software Center in a more timely fashion after imaging
# The default polling interval is 60 min
# Not sure which one of these is the actual solution and which are red herrings
# but I THINK it's the Data Discovery Collection Cycle that solves the issue
<#
https://gallery.technet.microsoft.com/scriptcenter/Start-SCCM-Client-Actions-d3d84c3c#content
SCCM Client Action Trigger Codes
--------------------------------
1 - {00000000-0000-0000-0000-000000000001} Hardware Inventory - (ConfigMgr Control Panel Applet - Hardware Inventory Cycle)
2 - {00000000-0000-0000-0000-000000000002} Software Inventory - (ConfigMgr Control Panel Applet - Software Inventory Cycle)
3 - {00000000-0000-0000-0000-000000000003} Discovery Inventory - (ConfigMgr Control Panel Applet - Discovery Data Collection Cycle)
4 - {00000000-0000-0000-0000-000000000010} File Collection - (ConfigMgr Control Panel Applet - File Collection Cycle)
5 - {00000000-0000-0000-0000-000000000011} IDMIF Collection
6 - {00000000-0000-0000-0000-000000000012} Client Machine Authentication
7 - {00000000-0000-0000-0000-000000000021} Request Machine Assignments - (ConfigMgr Control Panel Applet - Machine Policy Retrieval & Evaluation Cycle)
8 - {00000000-0000-0000-0000-000000000022} Evaluate Machine Policies
9 - {00000000-0000-0000-0000-000000000023} Refresh Default MP Task
10 - {00000000-0000-0000-0000-000000000024} LS (Location Service) Refresh Locations Task
11 - {00000000-0000-0000-0000-000000000025} LS (Location Service) Timeout Refresh Task
12 - {00000000-0000-0000-0000-000000000026} Policy Agent Request Assignment (User)
13 - {00000000-0000-0000-0000-000000000027} Policy Agent Evaluate Assignment (User) - (ConfigMgr Control Panel Applet - User Policy Retrieval & Evaluation Cycle)
14 - {00000000-0000-0000-0000-000000000031} Software Metering Generating Usage Report
15 - {00000000-0000-0000-0000-000000000032} Source Update Message - (ConfigMgr Control Panel Applet - Windows Installer Source List Update Cycle)
16 - {00000000-0000-0000-0000-000000000037} Clearing Proxy Settings Cache
17 - {00000000-0000-0000-0000-000000000040} Machine Policy Agent Cleanup
18 - {00000000-0000-0000-0000-000000000041} User Policy Agent Cleanup
19 - {00000000-0000-0000-0000-000000000042} Policy Agent Validate Machine Policy/Assignment
20 - {00000000-0000-0000-0000-000000000043} Policy Agent Validate User Policy/Assignment
21 - {00000000-0000-0000-0000-000000000051} Retrying/Refreshing Certificates in AD on MP
22 - {00000000-0000-0000-0000-000000000061} Peer DP Status Reporting
23 - {00000000-0000-0000-0000-000000000062} Peer DP Pending Package Check Schedule
24 - {00000000-0000-0000-0000-000000000063} SUM Updates Install Schedule
25 - {00000000-0000-0000-0000-000000000071} NAP action
26 - {00000000-0000-0000-0000-000000000101} Hardware Inventory Collection Cycle
27- {00000000-0000-0000-0000-000000000102} Software Inventory Collection Cycle
28 - {00000000-0000-0000-0000-000000000103} Discovery Data Collection Cycle
29 - {00000000-0000-0000-0000-000000000104} File Collection Cycle
30 - {00000000-0000-0000-0000-000000000105} IDMIF Collection Cycle
31 - {00000000-0000-0000-0000-000000000106} Software Metering Usage Report Cycle
32 - {00000000-0000-0000-0000-000000000107} Windows Installer Source List Update Cycle
33 - {00000000-0000-0000-0000-000000000108} Software Updates Assignments Evaluation Cycle - (ConfigMgr Control Panel Applet - Software Updates Deployment Evaluation Cycle)
34 - {00000000-0000-0000-0000-000000000109} Branch Distribution Point Maintenance Task
35 - {00000000-0000-0000-0000-000000000110} DCM Policy
36 - {00000000-0000-0000-0000-000000000111} Send Unsent State Message
37 - {00000000-0000-0000-0000-000000000112} State System Policy Cache Cleanout
38 - {00000000-0000-0000-0000-000000000113} Scan by Update Source - (ConfigMgr Control Panel Applet - Software Updates Scan Cycle)
39 - {00000000-0000-0000-0000-000000000114} Update Store Policy
40 - {00000000-0000-0000-0000-000000000115} State System Policy Bulk Send High
41 - {00000000-0000-0000-0000-000000000116} State System Policy Bulk Send Low
42 - {00000000-0000-0000-0000-000000000120} AMT Status Check Policy
43 - {00000000-0000-0000-0000-000000000121} Application Manager Policy Action - (ConfigMgr Control Panel Applet - Application Deployment Evaluation Cycle)
44 - {00000000-0000-0000-0000-000000000122} Application Manager User Policy Action
45 - {00000000-0000-0000-0000-000000000123} Application Manager Global Evaluation Action
46 - {00000000-0000-0000-0000-000000000131} Power Management Start Summarizer
47 - {00000000-0000-0000-0000-000000000221} Endpoint Deployment Reevaluate
48 - {00000000-0000-0000-0000-000000000222} Endpoint AM Policy Reevaluate
49 - {00000000-0000-0000-0000-000000000223} External Event Detection
#>
log "------------------------------------------"
log "Request Machine Assignments - (ConfigMgr Control Panel Applet - Machine Policy Retrieval & Evaluation Cycle)"
Invoke-WMIMethod -Namespace root\ccm -Class SMS_CLIENT -Name TriggerSchedule -ArgumentList '{00000000-0000-0000-0000-000000000021}' -Verbose 2>&1
#log "Waiting 5 minutes..."
#Start-Sleep -Seconds 300
# Start-Sleep was hanging the script indefinitely on some systems (Latitude 7400)
#ping "127.0.0.1" -n 300 | Out-Null
# ping-based wait is still hanging the script on the Latitude 7400s
log "------------------------------------------"
log "Discovery Inventory - (ConfigMgr Control Panel Applet - Discovery Data Collection Cycle)"
Invoke-WMIMethod -Namespace root\ccm -Class SMS_CLIENT -Name TriggerSchedule -ArgumentList '{00000000-0000-0000-0000-000000000003}' -Verbose 2>&1
#log "Waiting 5 minutes..."
#Start-Sleep -Seconds 300
# Start-Sleep was hanging the script indefinitely on some systems (Latitude 7400)
#ping "127.0.0.1" -n 300 | Out-Null
# ping-based wait is still hanging the script on the Latitude 7400s
log "------------------------------------------"
log "Application Manager Policy Action - (ConfigMgr Control Panel Applet - Application Deployment Evaluation Cycle)"
Invoke-WMIMethod -Namespace root\ccm -Class SMS_CLIENT -Name TriggerSchedule -ArgumentList '{00000000-0000-0000-0000-000000000121}' -Verbose 2>&1
log "------------------------------------------"
}
# Runs windows update powershell script
if($SkipWU) {
log "Skipping Windows Updates."
}
else {
log "Running update-windows.ps1..."
$wuscriptlog = "$engritLogPath\post-action-script-update-windows-script.log"
log "Log of update-windows.ps1 will go to $($wuscriptlog)..."
$wuinstalledlog = "$engritLogPath\post-action-script-windows-updates-installed.log"
log "Log of installed Windows Updates will go to $($wuinstalledlog)..."
$wumoduledir = "$scripts\update-windows-post-ts\PSWindowsUpdate"
powershell.exe -executionpolicy bypass -file "$scripts\update-windows-post-ts\update-windows-post-ts.ps1" -moduledir $wumoduledir -log $wuinstalledlog > $wuscriptlog 2>&1
}
# Removes pre-login message
log "Removing WU login warning..."
reg delete HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\policies\system /v legalnoticecaption /f
reg delete HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\policies\system /v legalnoticetext /f
# Unjoins domain if specified
if($UnjoinDomain -ne 'false') {
log "Running unjoin-domain.ps1..."
$unjoinlog = "$engritLogPath\post-action-script-unjoin-domain.log"
log "Log of unjoin-domain.ps1 will go to $($unjoinlog)..."
powershell.exe -ExecutionPolicy Bypass -file "$scripts\unjoin-domain.ps1" -user $UnjoinUser -pass $UnjoinPass -workgroup $UnjoinDomain > $unjoinlog 2>&1
}
# Calculate time spent imaging
log "Calculating time spent imaging..."
$endtime = get-date -uformat "%Y-%m-%d %T"
log "Current time is $($endtime)."
$runtime = 'unknown'
if($StartTime -ne 'unknown') {
log "Start time was $StartTime."
$dur = new-timespan -Start $StartTime -End $endtime
log "Duration was $dur."
$runtime = "$($dur.Days)d $($dur.Hours)h $($dur.Minutes)m $($dur.Seconds)s"
}
else {
log "Start time was unknown."
}
log "Runtime was $runtime."
$addinfo1 = "Named: $env:computername"
$addinfo2 = "Runtime: $runtime"
# Send completion email
if($Email -ne 'false') {
log "Sending completion email to $($Email)..."
$subj = "$ObjectName finished imaging"
$body = "$ObjectName finished imaging with $TS at $endtime. $addinfo1, $addinfo2"
$from = "noreply@illinois.edu"
$smtpserver = "express-smtp.cites.illinois.edu"
send-mailmessage -from $from -to $email -subject $subj -body $body -SmtpServer $smtpserver
}
# Copy CCM logs to c:\engrit\logs for convenience and as a snapshot of the logs post-image
# Moved here from the TS because the logs were incomplete since the TS wasn't done
log "Making copy of SMSTS and CCM logs..."
$timestamp = get-date -uformat "%Y-%m-%d_%H-%M-%s"
$copyPrefix = "ccm-logs-snapshot-($timestamp)"
$logFolderName = "$engritLogPath\$copyPrefix-logs-folder"
mkdir $logFolderName 2>&1
# A few extra copies of the most important logs, right in c:\engrit\logs for even more convenience
robocopy /r:3 /w:5 $smstsLogPath $logFolderName 2>&1
robocopy /r:3 /w:5 $smstsLogPath $engritLogPath "smsts*.log" 2>&1
Get-ChildItem -Path "$engritLogPath\smsts*.log" | Rename-Item -NewName { $_.name -Replace 'smsts', "$copyPrefix-smsts" }
robocopy /r:3 /w:5 $smstslogpath $engritLogPath "AppEnforce.log" 2>&1
Rename-Item -Path "$engritLogPath\AppEnforce.log" -NewName "$copyPrefix-AppEnforce.log"
# Set cmtrace.exe as default log viewer, if requested
if($CMTraceForLogs) {
$cmtracelog = "$engritLogPath\default-log-viewer.log"
# https://stackoverflow.com/questions/33571900/assoc-and-ftype-do-not-work-under-powershell
# https://docs.microsoft.com/en-us/previous-versions/technet-magazine/ff687021(v=msdn.10)
# https://docs.microsoft.com/en-us/windows/desktop/shell/fa-file-types
cmd /c ftype logfile="c:\windows\ccm\cmtrace.exe" "%1" > $cmtracelog 2>&1
cmd /c assoc .log=logfile >> $cmtracelog 2>&1
}
# Check smsts logs to find apps that installed successfully or failed
if ($AppInstallStatusMsg){
log "Checking logs for app install status..."
$PatternsToLookFor = @("Installing application '(?!ScopeId)", "Install application action (?!cannot)", "App installation")
$Files = Get-ChildItem $smstsLogPath\smsts*.log
$AppInstallLogs = Get-CMLog -Path $Files -Patterns $PatternsToLookFor
$AppInstallLogs = $AppInstallLogs | Sort LocalTime
$AppsInstalledSuccessfully = @()
$AppsFailedToInstall = @()
For ($i = 0; $i -lt $AppInstallLogs.Length; $i++)
{
log "App log $i $AppInstallLogs[$i].message"
If($AppInstallLogs[$i].message -like "Install application action completed successfully*" -or $AppInstallLogs[$i].message -like "App installation successful*")
{
$AppsInstalledSuccessfully += [regex]::Match($AppInstallLogs[$i-1].message, "(?<=')(.*)(?=')").Groups[1].Value
}
ElseIf ($smsts[$i].message -like "Install application action failed*")
{
$AppsFailedToInstall += [regex]::Match($AppInstallLogs[$i-1].message, "(?<=')(.*)(?=')").Groups[1].Value
}
}
$addinfo3 = ""
if($AppsInstalledSuccessfully -gt 0) {
$addinfo3 += " Successfully installed: $($AppsInstalledSuccessfully -join ',')."
}
if($AppsFailedToInstall -gt 0) {
$addinfo3 += " Failed to install: $($AppsFailedToInstall -join ',')."
}
log "addinfo3 is $addinfo3"
}
# Push completion notification to Slack
if($SlackMsg -ne 'false') {
log "Pushing notification to Slack..."
$slacklog = "$engritLogPath\post-to-slack.ps1-end.log"
log "Log will go to $($slacklog)..."
powershell.exe -executionpolicy bypass -file "$scripts\post-to-slack.ps1" -message "$SlackMsg (_$addinfo1, *$addinfo2*$addinfo3`_)" -channel $SlackChannel -name $SlackName -icon $SlackIcon -webhook $SlackWebhook > $slacklog 2>&1
}
# Push completion notification to Teams
if($TeamsMsg -ne 'false') {
log "Pushing notification to Teams..."
$teamslog = "$engritLogPath\post-to-teams-end.log"
log "Log will go to $($teamslog)..."
powershell.exe -executionpolicy bypass -file "$scripts\post-to-teams.ps1" -message "$TeamsMsg (_$addinfo1, *$addinfo2*$addinfo3`_)" -title $TeamsName -webhook $TeamsWebhook > $teamslog 2>&1
}
if($HighPower) {
$powerplanlog = "$engritLogPath\set-high-performance-power-plan.log"
log "Log will go to $($powerplanlog)..."
powershell.exe -executionpolicy bypass -file "$scripts\set-high-performance-power-plan.ps1" > $powerplanlog 2>&1
}
# forces immediate computer reboot
log "Rebooting..."
shutdown /r /t 0
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment