The Script Template To Rule Them All

Hi guys! First post on the community forums, but I thought I’d share a system I’ve been using and tweaking for a year now. I take no credit for all the functions in this script. Many of them are adapted from the community scripts and other sources.

One of the issues I had when we moved to Syncro is cleaning up scripts we incorporated into our environment. Some scripts create a temp on the root of the drive, others use the user temp directory, others C:\Windows\Temp. I wanted a clean interface for adapting and implementing scripts, but more importantly I wanted to keep our client environment clean and organized. I needed something for our techs learning PowerShell to quickly and easily adapt to, without a huge learning curve

Initially I had four templates for use, Standard script, Randomized Start script, Ticket Creation script, and Maintenance ticket script. We’ve combined all of this into one tidy script, ready for use.

Features of this script:

Simple Template with Variables at the top for easy adoption. Simply change the variables for the script functions you want, and add the script code to the content box.

Filesystem Standardization. This Script creates the following directories on the asset for easy organization and reference:

  • C:\ProgramData\yoursubdomain\Apps
  • C:\ProgramData\yoursubdomain\Data
  • C:\ProgramData\yoursubdomain\Installers
  • C:\ProgramData\yoursubdomain\Logs
    This allows you to set the paths of your downloaded files and output files for your scripts to be in a referenceable location. We run a monthly maintenance script on our log directory to only keep 30 days of logs. You can specify if you want the folder to be Title Case or UPPERCASE.

Store Logs Locally. You can select Local Logging for the script to output a timestamped log to the log directory. This is useful for future reference and troubleshooting. Not recommended if any sensitive data is outputted to clear text.

Script Execution Timer. Each script runs a timer that measures the amount of time the script takes to run. If applied to a ticket, this number is rounded to the nearest minute (it seems Syncro doesn’t allow tenths of a minute in the variable for duration. Can this be fixed please!?!).

Log Activity Notification. The Script outputs the Script Name and the Execution Time to the Log Activity
Screenshot 2021-12-22 075820

Random Start Time. Sometimes when we are ingesting a lot of endpoint data, especially with uploads to the asset, we hit Syncro’s wall and get the dreaded “too many requests”. We added this ability to spread out the initial start of scheduled scripts to mitigate this for larger sites.

Monthly Maintenance Ticket. Not going to lie, I hesitated sharing this as I think it’s an advantage, but at the end of the day, MSPs and IT personnel can be silos or partners. I’d like for us to always be a resource to those in the field. This feature creates a ticket at the beginning of each month, then any script with this variable enabled will clock time against this ticket. It auto-resolves it after the activity, so check your settings on ticket notifications, or this will go bad for you.
This script creates a ticket if one doesn’t exist. If one exists, it checks to see if it was created in the same month as when the script is being run. If it does not, it deletes the local marker and creates a new Maintenance Ticket.

Create Standalone Ticket. You can also have the script create a standalone ticket, as a response to an RMM Alert.

Billable Time for Script. You can select whether the time on this script is billable.

Manual Override for Activity Duration Want to charge a set amount of minutes for a script? Enter it into the override and all timer reports will match your custom time.

Auto-close RMM Alerts. Enabling this allows the script to close the RMM alert for the category you define for the asset.

Set your TechID for Tickets and Activity. Just a note, we use the userID instead of the email, it’s cleaner. The script instructs how to find yours.

Easier Variables in Scripts. Because we define so many variables, it makes it easy to reference in your script commands. $comp is the computername, $MSPAppsDir is the App directory in the folder structure. This makes adapting and referencing your scripts easy!

Easy to Read Output. I’ve done extra work to the time output and other readable text to make it very easy to understand.

That’s it! Please let me know your thoughts and feedback!

10 Likes

Without Further Ado, the script:

##############################################################
## SECTION - Variable Setup
# Enter your Syncro subdomain here
$SyncroSubDomain = "subdomainhere"
# Enter your Script Name and Description from above
$ScriptName = "Your Script Title"
$ScriptDesc = "This is what I do"
# Do you want to generate a local log for this script (true/false)?
$ScriptLocalLogs = "false"
# Do you want a random delay for this script to avoid too many concurrent connections (true/false)?
## If so, change the minimum/maximum time for the random counter in seconds (default is 5 minutes)
$ScriptRandomDelay = "false"
$RandomDelay = (Get-Random -Minimum 5 -Maximum 300)
# Do you want this to go against a monthly maintenance ticket (true/false)?
$ScriptMonthlyMaintenance = "false"
# Do you want this script to create a standalone ticket (true/false)?
## If so, enter the ticket details. For line breaks in the comment body, use 'r'n
$ScriptCreateTicket = "false"
$SyncroTicketSubject = "Necessary action needed on $env:computername"
$SyncroTicketBody = "An issue has been identified on this asset by an automated trigger or technician. In order to remediate the issue, $ScriptName needs to be performed on the device."
$SyncroIssueType = "Maintenance"
# Is this a chargeable activity (true/false)?
$SyncroTicketActivityCharge = "false"
# Do you want to set a specific amount of time (in minutes) for this script activity (true/false)?
## If you do not specify, it will use the actual execution time of the script.
$ScriptActivityTimeManual = "false"
$ScriptActivityDuration = "5"
# Do you want this script to close any associated RMM Alerts (true/false)?
$RMMCloseAlert = "false"
$RMMAlertCategory = "YourCategory"
# Enter the Syncro User ID that any Activity time will be logged against.
## Your UserID can be found by hover your mouse over the User in Syncro Admin and note the ID in the URL link.
$SyncroTechID = 123456789
# This script will create a default filepath for all scipts and files for standard deployment.
## Uncomment which format you wish for your Folder, Title Case or UPPERCASE
$MSPName = (Get-Culture).TextInfo.ToTitleCase($SyncroSubDomain)
# $MSPName = $SyncroSubDomain.ToUpper()
$MSPRootDir = "$env:ProgramData\$MSPName"
$MSPAppsDir = "$env:ProgramData\$MSPName\Apps"
$MSPDataDir = "$env:ProgramData\$MSPName\Data"
$MSPInstDir = "$env:ProgramData\$MSPName\Installers"
$MSPLogsDir = "$env:ProgramData\$MSPName\Logs"
# Quick variable to call the local computername.
$comp = $env:computername
# No more variable changes beyond here.
##############################################################
## SECTION - Script Code to Execute
$ScriptExecutionContent=@'
##### DO NOT EDIT ABOVE THIS LINE ##### ##### DO NOT EDIT ABOVE THIS LINE ##### ##### DO NOT EDIT ABOVE THIS LINE #####
############################################################################################################################

Write-Output "Hello World!"

############################################################################################################################
##### DO NOT EDIT BELOW THIS LINE ##### ##### DO NOT EDIT BELOW THIS LINE ##### ##### DO NOT EDIT BELOW THIS LINE #####
'@
Import-Module $env:SyncroModule
##############################################################
## SECTION - Script Timer Start
$stopWatch = New-Object -TypeName System.Diagnostics.Stopwatch
$stopWatch.start()
##############################################################
## SECTION - Filepath Validation
# This section ensures that the necessary folders are in place 
If(!(test-path $MSPRootDir)) {New-Item -ItemType Directory -Force -Path $MSPRootDir}
If(!(test-path $MSPAppsDir)) {New-Item -ItemType Directory -Force -Path $MSPAppsDir}
If(!(test-path $MSPDataDir)) {New-Item -ItemType Directory -Force -Path $MSPDataDir}
If(!(test-path $MSPInstDir)) {New-Item -ItemType Directory -Force -Path $MSPInstDir }
If(!(test-path $MSPLogsDir)) {New-Item -ItemType Directory -Force -Path $MSPLogsDir}
##############################################################
## SECTION - Logging
$ScriptDateTime = Get-Date -Format "yyyyMMdd-HH.mm"
if ($ScriptLocalLogs -eq "true") {
    $ErrorActionPreference = "Continue"
    $ScriptLogPath = "$MSPLogsDir\$ScriptDateTime-$ScriptName.log"
	Start-Transcript -path $ScriptLogPath
	}
##############################################################
## SECTION - Script Delay
if ($ScriptRandomDelay -eq "true") {
	$SleepTimerMinutes = [timespan]::fromseconds($RandomDelay).tostring("m' minutes and 's' seconds'")
	Write-Output "===== Script will wait for $SleepTimerMinutes before continuing"
	Start-Sleep -s $RandomDelay
	}
##############################################################
## SECTION - Monthly Maintenance Ticket
if ($ScriptMonthlyMaintenance -eq "true") {
	$SyncroTicketProcess = "true"
	$NewMaintTicket = "false"
	If(!(test-path $MSPDataDir\MonthlyMaintTicket.txt)) {
		$NewMaintTicket = "true"
		} else {
		$CurrentMonth = Get-Date -Format "MM"
		$FileMonth = (Get-Item $MSPDataDir\MonthlyMaintTicket.txt).lastwritetime.month
		if(($CurrentMonth - $FileMonth) -eq "0") {
		Write-Output "===== Maintenance TicketID is valid"} else {
		Write-Output "===== Maintenance TicketID is from outside this month, creating new Maintenance ticket"
		$NewMaintTicket = "true"}
	}
	if ($NewMaintTicket -eq "true") {
		Write-Output "===== New maintenance ticket needed, removing any existing maintenance TicketID"
		If(test-path $MSPDataDir\MonthlyMaintTicket.txt) {Remove-Item –path $MSPDataDir\MonthlyMaintTicket.txt -force}
		Write-Output "===== No existing maintenance ticket, creating one now"
		$SyncroTicketSubject = "Automation: Monthly Maintenance on $comp"
		$SyncroTicketCommandResult = Create-Syncro-Ticket -Subject "$SyncroTicketSubject" -IssueType "$SyncroIssueType" -Status "New" -DoNotEmail "true"
		Write-Output $SyncroTicketCommandResult
		$SyncroTicketID = $SyncroTicketCommandResult.ticket.number
		$SyncroCustomerName = $SyncroTicketCommandResult.ticket.customer_id
		$SyncroTicketID | Out-File $MSPDataDir\MonthlyMaintTicket.txt
		Write-Output "===== Maintenance Ticket $SyncroTicketID created"
	} else {$SyncroTicketID = Get-Content $MSPDataDir\MonthlyMaintTicket.txt}
	Write-Output "===== Updating Ticket $SyncroTicketID to In Progress"
	Update-Syncro-Ticket -TicketIdOrNumber $SyncroTicketID -Status "In Progress"
}
##############################################################
## SECTION - Create Standalone Ticket
if ($ScriptCreateTicket -eq "true") {
	$SyncroTicketProcess = "true"
	$SyncroTicketCommandResult = Create-Syncro-Ticket -Subject "$SyncroTicketSubject" -Body "$SyncroTicketBody" -IssueType "$SyncroIssueType" -Status "New" -DoNotEmail "true"
	Write-Output $SyncroTicketCommandResult
	$SyncroTicketID = $SyncroTicketCommandResult.ticket.number
	$SyncroCustomerName = $SyncroTicketCommandResult.ticket.customer_id
	Write-Output "===== Updating Ticket $SyncroTicketID to In Progress"
	Update-Syncro-Ticket -TicketIdOrNumber $SyncroTicketID -Status "In Progress"
	}
##############################################################
## SECTION - Running Script Code
Write-Output "===== Executing: $ScriptName"
Invoke-Expression $ScriptExecutionContent
Write-Output "===== Completed: $ScriptName"
##############################################################
## SECTION - Script Activity Logging
$stopWatch.stop()
$ScriptTimer = ([math]::round(($stopWatch.ElapsedMilliseconds / 1000),1))
Write-Output "===== Script Timer reports $ScriptTimer seconds"
if ($ScriptActivityTimeManual -eq "true") {$ScriptTimer = ($ScriptActivityDuration * 60)} else {
    if($ScriptTimer -lt "60") {$ScriptActivityDuration = 1} else {$ScriptActivityDuration = ([math]::Round(($ScriptTimer / 60)))}}
	Write-Output "===== Script Activity Duration is $ScriptActivityDuration minutes"
$ScriptTimerMinutes = [timespan]::fromseconds($ScriptTimer).tostring("m' minutes and 's' seconds'")
Write-Output "===== Execution of $ScriptName took $ScriptTimerMinutes ($ScriptActivityDuration minutes, or $ScriptTimer seconds)"
Log-Activity -Message "Execution of $ScriptName took $ScriptTimerMinutes" -EventName "Script Activity"
##############################################################
## SECTION - Syncro Ticket Activity
if ($SyncroTicketProcess -eq "true") {
	Write-Output "===== Creating Comment on Ticket $SyncroTicketID"
	$SyncroCommentSubject = "Executing Task: $SyncroScriptName on $comp"
	$SyncroCommentBody = "Performing Requested Activity $SyncroScriptName on $comp. Execution of $ScriptName took $ScriptTimerMinutes"
	$SyncroTicketActivityNote = "Performing Requested Activity $SyncroScriptName on $comp."
	Create-Syncro-Ticket-Comment -TicketIdOrNumber $SyncroTicketID -Subject "$SyncroCommentSubject" -Body "$SyncroCommentBody" -Hidden "false" -DoNotEmail "true"
	Write-Output "===== Adding Timer Entry for $ScriptActivityDuration on Ticket $SyncroTicketID"
	$SyncroTicketStartTime = (Get-Date).AddMinutes(-$ScriptActivityDuration).toString("yyyy-MM-dd HH:mm")
	Create-Syncro-Ticket-TimerEntry -TicketIdOrNumber $SyncroTicketID -StartTime "$SyncroTicketStartTime" -DurationMinutes "$ScriptActivityDuration" -Notes "$SyncroTicketActivityNote" -UserIdOrEmail "$SyncroTechID" -ChargeTime $SyncroTicketActivityCharge
    Write-Output "===== Updating Ticket $SyncroTicketID to Resolved"
	Update-Syncro-Ticket -TicketIdOrNumber $SyncroTicketID -Status "Resolved"
}
##############################################################
## SECTION - Close RMM Alert
if ($RMMCloseAlert -eq "true") {
Write-Output "===== Closing RMM Alert for $comp in category $RMMAlertCategory"
Close-Rmm-Alert -Category "$RMMAlertCategory" -CloseAlertTicket "true"
}
##############################################################
## SECTION - Stop Logging
if ($ScriptLocalLogs -eq "true") {Stop-Transcript}

3 Likes

As an example, here’s our Autopilot Script, which pulls the data needed for Intune’s Autopilot from the asset, using our existing script template. We are enabling this as part of our Maintenance Ticket with local Logging, and a random start since it uploads a record to the asset.

Screenshot 2021-12-22 085623

##############################################################
## SECTION - Variable Setup
# Enter your Syncro subdomain here
$SyncroSubDomain = "subdomain"
# Enter your Script Name and Description from above
$ScriptName = "Audit - AutoPilot ID Export"
$ScriptDesc = "This script collects the necessary AutoPilot Information for this device and attaches it to the Asset Record."
# Do you want to generate a local log for this script (true/false)?
$ScriptLocalLogs = "true"
# Do you want a random delay for this script to avoid too many concurrent connections (true/false)?
## If so, change the minimum/maximum time for the random counter in seconds (default is 5 minutes)
$ScriptRandomDelay = "true"
$RandomDelay = (Get-Random -Minimum 5 -Maximum 300)
# Do you want this to go against a monthly maintenance ticket (true/false)?
$ScriptMonthlyMaintenance = "true"
# Do you want this script to create a standalone ticket (true/false)?
## If so, enter the ticket details. For line breaks in the comment body, use 'r'n
$ScriptCreateTicket = "false"
$SyncroTicketSubject = "Necessary action needed on $env:computername"
$SyncroTicketBody = "An issue has been identified on this asset by an automated trigger or technician. In order to remediate the issue, $ScriptName needs to be performed on the device."
$SyncroIssueType = "Alert-Maintenance"
# Is this a chargeable activity (true/false)?
$SyncroTicketActivityCharge = "false"
# Do you want to set a specific amount of time (in minutes) for this script activity (true/false)?
## If you do not specify, it will use the actual execution time of the script.
$ScriptActivityTimeManual = "false"
$ScriptActivityDuration = "5"
# Do you want this script to close any associated RMM Alerts (true/false)?
$RMMCloseAlert = "false"
$RMMAlertCategory = "YourCategory"
# Enter the Syncro User ID that any Activity time will be logged against.
## Your UserID can be found by hover your mouse over the User in Syncro Admin and note the ID in the URL link.
$SyncroTechID = 123456
# This script will create a default filepath for all scipts and files for standard deployment.
## Uncomment which format you wish for your Folder, Title Case or UPPERCASE
$MSPName = (Get-Culture).TextInfo.ToTitleCase($SyncroSubDomain)
# $MSPName = $SyncroSubDomain.ToUpper()
$MSPRootDir = "$env:ProgramData\$MSPName"
$MSPAppsDir = "$env:ProgramData\$MSPName\Apps"
$MSPDataDir = "$env:ProgramData\$MSPName\Data"
$MSPInstDir = "$env:ProgramData\$MSPName\Installers"
$MSPLogsDir = "$env:ProgramData\$MSPName\Logs"
# Quick variable to call the local computername.
$comp = $env:computername
# No more variable changes beyond here.
##############################################################
## SECTION - Script Code to Execute
$ScriptExecutionContent=@'
##### DO NOT EDIT ABOVE THIS LINE ##### ##### DO NOT EDIT ABOVE THIS LINE ##### ##### DO NOT EDIT ABOVE THIS LINE #####
############################################################################################################################

##############################################################
## START SECTION - AutoPilot Info
Write-Output "===== Getting AutoPilot Information"
$bad = $false
$session = New-CimSession
$serial = (Get-CimInstance -CimSession $session -Class Win32_BIOS).SerialNumber
$devDetail = (Get-CimInstance -CimSession $session -Namespace root/cimv2/mdm/dmmap -Class MDM_DevDetail_Ext01 -Filter "InstanceID='Ext' AND ParentID='./DevDetail'")
if ($devDetail -and (-not $Force)) {
    $hash = $devDetail.DeviceHardwareData
    } else {
    $bad = $true
    $hash = ""
    }
if ($bad -or $Force) {
    $cs = Get-CimInstance -CimSession $session -Class Win32_ComputerSystem
    $make = $cs.Manufacturer.Trim()
    $model = $cs.Model.Trim()
    if ($Partner) {
        $bad = $false
    }
    } else {
    $make = ""
    $model = ""
    }
$product = ""
$c = New-Object psobject -Property @{
    "Device Serial Number" = $serial
    "Windows Product ID" = $product
    "Hardware Hash" = $hash
    }
if ($bad){
    Write-Error -Message "===== Unable to retrieve device hardware data (hash) from computer $comp" -Category DeviceError
    } elseif ($OutputFile -eq ""){$c} else {
    $computers = $c
    Write-Output "Gathered details for device with serial number: $serial"
    }
Remove-CimSession $session
$computers | Select "Device Serial Number", "Windows Product ID", "Hardware Hash" | ConvertTo-CSV -NoTypeInformation | % {$_ -replace '"',''} | Out-File $MSPDataDir\AutoPilotInfo.csv
Write-Output "===== Uploading AutoPilotInfo.csv to Asset"
Upload-File -FilePath "$MSPDataDir\AutoPilotInfo.csv"

############################################################################################################################
##### DO NOT EDIT BELOW THIS LINE ##### ##### DO NOT EDIT BELOW THIS LINE ##### ##### DO NOT EDIT BELOW THIS LINE #####
'@
Import-Module $env:SyncroModule
##############################################################
## SECTION - Script Timer Start
$stopWatch = New-Object -TypeName System.Diagnostics.Stopwatch
$stopWatch.start()
##############################################################
## SECTION - Filepath Validation
# This section ensures that the necessary folders are in place 
If(!(test-path $MSPRootDir)) {New-Item -ItemType Directory -Force -Path $MSPRootDir}
If(!(test-path $MSPAppsDir)) {New-Item -ItemType Directory -Force -Path $MSPAppsDir}
If(!(test-path $MSPDataDir)) {New-Item -ItemType Directory -Force -Path $MSPDataDir}
If(!(test-path $MSPInstDir )) {New-Item -ItemType Directory -Force -Path $MSPInstDir }
If(!(test-path $MSPLogsDir)) {New-Item -ItemType Directory -Force -Path $MSPLogsDir}
##############################################################
## SECTION - Logging
$ScriptDateTime = Get-Date -Format "yyyyMMdd-HH.mm"
if ($ScriptLocalLogs -eq "true") {
    $ErrorActionPreference = "Continue"
    $ScriptLogPath = "$MSPLogsDir\$ScriptDateTime-$ScriptName.log"
    Start-Transcript -path $ScriptLogPath
	}
##############################################################
## SECTION - Script Delay
if ($ScriptRandomDelay -eq "true") {
	$SleepTimerMinutes = [timespan]::fromseconds($RandomDelay).tostring("m' minutes and 's' seconds'")
	Write-Output "===== Script will wait for $SleepTimerMinutes before continuing"
	Start-Sleep -s $RandomDelay
	}
##############################################################
## SECTION - Monthly Maintenance Ticket
if ($ScriptMonthlyMaintenance -eq "true") {
	$SyncroTicketProcess = "true"
	$NewMaintTicket = "false"
	If(!(test-path $MSPDataDir\MonthlyMaintTicket.txt)) {
		$NewMaintTicket = "true"
		} else {
		$CurrentMonth = Get-Date -Format "MM"
		$FileMonth = (Get-Item $MSPDataDir\MonthlyMaintTicket.txt).lastwritetime.month
		if(($CurrentMonth - $FileMonth) -eq "0") {
		Write-Output "Maintenance TicketID is valid"} else {
		Write-Output "Maintenance TicketID is from outside this month, creating new Maintenance ticket"
		$NewMaintTicket = "true"}
	}
	if ($NewMaintTicket -eq "true") {
		Write-Output "New maintenance ticket needed, removing any existing maintenance TicketID"
		If(test-path $MSPDataDir\MonthlyMaintTicket.txt) {Remove-Item –path $MSPDataDir\MonthlyMaintTicket.txt -force}
		Write-Output "===== No existing maintenance ticket, creating one now"
		$SyncroTicketSubject = "Automation: Monthly Maintenance on $comp"
		$SyncroTicketCommandResult = Create-Syncro-Ticket -Subject "$SyncroTicketSubject" -IssueType "$SyncroIssueType" -Status "New" -DoNotEmail "true"
		Write-Output $SyncroTicketCommandResult
		$SyncroTicketID = $SyncroTicketCommandResult.ticket.number
		$SyncroCustomerName = $SyncroTicketCommandResult.ticket.customer_id
		$SyncroTicketID | Out-File $MSPDataDir\MonthlyMaintTicket.txt
		Write-Output "===== Maintenance Ticket $SyncroTicketID created"
	} else {$SyncroTicketID = Get-Content $MSPDataDir\MonthlyMaintTicket.txt}
	Write-Output "===== Updating Ticket $SyncroTicketID to In Progress"
	Update-Syncro-Ticket -TicketIdOrNumber $SyncroTicketID -Status "In Progress"
}
##############################################################
## SECTION - Create Standalone Ticket
if ($ScriptCreateTicket -eq "true") {
	$SyncroTicketProcess = "true"
	$SyncroTicketCommandResult = Create-Syncro-Ticket -Subject "$SyncroTicketSubject" -Body "$SyncroTicketBody" -IssueType "$SyncroIssueType" -Status "New" -DoNotEmail "true"
	Write-Output $SyncroTicketCommandResult
	$SyncroTicketID = $SyncroTicketCommandResult.ticket.number
	$SyncroCustomerName = $SyncroTicketCommandResult.ticket.customer_id
	Write-Output "===== Updating Ticket $SyncroTicketID to In Progress"
	Update-Syncro-Ticket -TicketIdOrNumber $SyncroTicketID -Status "In Progress"
	}
##############################################################
## SECTION - Running Script Code
Write-Output "===== Executing: $ScriptName"
Invoke-Expression $ScriptExecutionContent
Write-Output "===== Completed: $ScriptName"
##############################################################
## SECTION - Script Activity Logging
$stopWatch.stop()
$ScriptTimer = ([math]::round(($stopWatch.ElapsedMilliseconds / 1000),1))
Write-Output "Script Timer reports $ScriptTimer seconds"
if ($ScriptActivityTimeManual -eq "true") {$ScriptTimer = ($ScriptActivityDuration * 60)} else {
    if($ScriptTimer -lt "60") {$ScriptActivityDuration = 1} else {$ScriptActivityDuration = ([math]::Round(($ScriptTimer / 60)))}}
	Write-Output "Script Activity Duration is $ScriptActivityDuration minutes"
$ScriptTimerMinutes = [timespan]::fromseconds($ScriptTimer).tostring("m' minutes and 's' seconds'")
Write-Output "Execution of $ScriptName took $ScriptTimerMinutes ($ScriptActivityDuration minutes, or $ScriptTimer seconds)"
Log-Activity -Message "Execution of $ScriptName took $ScriptTimerMinutes" -EventName "Script Activity"
##############################################################
## SECTION - Syncro Ticket Activity
if ($SyncroTicketProcess -eq "true") {
	Write-Output "===== Creating Comment on Ticket $SyncroTicketID"
	$SyncroCommentSubject = "Executing Task: $SyncroScriptName on $comp"
	$SyncroCommentBody = "Performing Requested Activity $SyncroScriptName on $comp. Execution of $ScriptName took $ScriptTimerMinutes"
	$SyncroTicketActivityNote = "Performing Requested Activity $SyncroScriptName on $comp."
	Create-Syncro-Ticket-Comment -TicketIdOrNumber $SyncroTicketID -Subject "$SyncroCommentSubject" -Body "$SyncroCommentBody" -Hidden "false" -DoNotEmail "true"
	Write-Output "===== Adding Timer Entry for $ScriptActivityDuration on Ticket $SyncroTicketID"
	$SyncroTicketStartTime = (Get-Date).AddMinutes(-$ScriptActivityDuration).toString("yyyy-MM-dd HH:mm")
	Create-Syncro-Ticket-TimerEntry -TicketIdOrNumber $SyncroTicketID -StartTime "$SyncroTicketStartTime" -DurationMinutes "$ScriptActivityDuration" -Notes "$SyncroTicketActivityNote" -UserIdOrEmail "$SyncroTechID" -ChargeTime $SyncroTicketActivityCharge
    Write-Output "===== Updating Ticket $SyncroTicketID to Resolved"
	Update-Syncro-Ticket -TicketIdOrNumber $SyncroTicketID -Status "Resolved"
}
##############################################################
## SECTION - Close RMM Alert
if ($RMMCloseAlert -eq "true") {
Write-Output "Closing RMM Alert for $comp in category $RMMAlertCategory"
Close-Rmm-Alert -Category "$RMMAlertCategory" -CloseAlertTicket "true"
}
##############################################################
## SECTION - Stop Logging
if ($ScriptLocalLogs -eq "true") {Stop-Transcript}
3 Likes

:clap: great work! thank you for sharing!

Wow amazing, thanks for sharing this!

Thanks for sharing. It’s too bad you lose the syntax highlighting on the core script (and therefore more likely to cause bugs when editing). Maybe you could use a function instead? Or does that break other things? I’d have to think that through lol. You might also want to think about creating a module for the bottom portion, would be easier to update all scripts that way. There may be downsides I’m not thinking of, haven’t actually taken the time to try doing so myself yet.

To clarify, you use this as the basis, and as a template, for all the other scripts you run - you basically drop them into the middle this one to standardize your environment?

If I understand this correctly, that is an interesting and useful addition to scripting, certainly for new clients. There would be work required to clean up existing systems that don’t follow the folder layout.

Thank you for posting.

The best way to handle this sort of a script is to use a script block instead of the hear string. If I recall Powershell Variables do not get evaluated until the script block is executed so it should work the same as a single quote hear-string. Also you can do something like $ScriptExecutionContent.ToString() if you need to spit out the script block as a string, but in this case since the block is just being called by Invoke-Expression I’m pretty sure a script block is the preferred syntax anyway.

ps. If someone does decide to convert the script to use a script block definitely do your own testing before deploying and don’t take my work for it.

2 Likes

Maybe you’d be the one who decides to convert the script to a block and posts it? :wink:

sorry wrong thread, intended to post this on a different issue. :man_facepalming:

Hey guys, sorry for the long delay. holiday and New Year’s shenanigans.

To @isaacg, I don’t edit the actual script I want to execute in the editor, I actually save them as individual scripts without the above wrapper.

@des, exactly. This way our team knows that C:\ProgramData\OURFOLDER is where all our script files and logs are stored. Makes is simple. We even instruct them when downloading a file to an asset to use the folder structure to keep all asset interaction in a controlled environment.

@jordanritz, sounds great but it’s out of my wheel house to know what to do. This method works.

I test as needed, then drop them into the template. The template has actually changed a bit since this post, I’ll update it soon.

you should literally be able to just replace the above code with

#######################################
##### SECTION - Script Code to Execute
$ScriptExecutionContent={
##### DO NOT EDIT ABOVE THIS LINE #####
######################################

Write-Output "Hello World!"

#######################################
##### DO NOT EDIT BELOW THIS LINE #####
}

Your template has helped me enormously. Would you be willing to post your update as you mentioned? I’m very much looking forward to see it’s progression. Thanks!

Thanks much anon37071023!

You were very close jordanritz. I switched to script block and it kicked an error. One more change was needed:

In the area:

##############################################################

SECTION - Running Script Code

Write-Output “===== Executing: $ScriptName”
Invoke-Expression $ScriptExecutionContent
Write-Output “===== Completed: $ScriptName”
##############################################################

I changed:

Invoke-Expression $ScriptExecutionContent

To:

$ScriptExecutionContent | Invoke-Expression