2023-10-05 13:11:38 -04:00
# Define the MSP name
2021-08-05 09:42:15 -04:00
[ string ] $Global:MSPName = "Emberkom"
2023-10-05 13:11:38 -04:00
# This section will define several global variables that can be used in scripts that source this one
2021-08-05 09:42:15 -04:00
[ string ] $Global:RootDirectory
[ string ] $Global:ScriptsDirectory
[ string ] $Global:OutputDirectory
[ string ] $Global:ToolsDirectory
[ string ] $Global:LogsDirectory
[ string ] $Global:LogFile
2020-09-09 21:06:44 -04:00
2023-09-15 16:46:10 -04:00
# Define the root path for the MSP in the Windows Registry
[ string ] $Global:MSPRegistryRoot = "HKEY_LOCAL_MACHINE\SOFTWARE\ ${MSPName} "
2024-06-25 16:06:37 -04:00
# Create a global variable to keep a ticket and billing related variables if they are needed later
2024-06-24 16:51:19 -04:00
$Global:TicketNumber = $null
2024-06-25 16:06:37 -04:00
$Global:TimeAttributedToUser = ""
2024-06-24 16:51:19 -04:00
2023-10-05 13:11:38 -04:00
# Setup the MSP management directories
2021-08-05 09:42:15 -04:00
Function Setup-MSPDirectories {
If ( Test-Path ${ Env : ProgramData } ) { $Global:RootDirectory = " ${Env:ProgramData} \ ${MSPName} " } Else { $Global:RootDirectory = " ${Env:SystemDrive} \ ${MSPName} " }
$Global:ScriptsDirectory = " ${RootDirectory} \Scripts"
$Global:OutputDirectory = " ${RootDirectory} \Output"
$Global:ToolsDirectory = " ${RootDirectory} \Tools"
$Global:LogsDirectory = " ${RootDirectory} \Logs"
2020-09-09 21:06:44 -04:00
2023-10-05 13:11:38 -04:00
# Make sure all the management directories exist
2021-08-05 09:42:15 -04:00
$RootDirectory , $ScriptsDirectory , $OutputDirectory , $ToolsDirectory , $LogsDirectory | ForEach-Object {
If ( !( Test-Path $_ ) ) {
Try { New-Item -Path $_ -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null }
Catch { Write-Output $_ . Exception . Message }
}
}
2020-09-23 21:31:52 -04:00
}
2020-09-09 21:06:44 -04:00
2023-10-05 13:11:38 -04:00
# Function to get a datestamp for right now in a long format
2021-08-05 09:42:15 -04:00
Function Get-LongDateTime {
Return $ ( Get-Date -Format "yyyyMMddHHmmssffff" )
}
2020-09-19 15:29:31 -04:00
2024-02-23 11:15:00 -05:00
# Function to get a datestanp for the current day
Function Get-LongDate {
Return $ ( Get-Date -Format "yyyyMMdd" )
}
2023-10-05 13:11:38 -04:00
# Setup the log file for messages generated when this script is run
2021-08-05 09:42:15 -04:00
Function Setup-LogFile {
$LogFilePrefix = "ManagementScript"
$LogFilePostfix = Get-LongDateTime
$Global:LogFile = " ${LogsDirectory} \ ${LogFilePrefix} _ ${LogFilePostfix} .log"
2023-10-05 13:11:38 -04:00
# Delete log files older than 120 days
2021-09-23 14:28:01 -04:00
$LogFileAllowedAge = ( Get-Date ). AddDays ( -120 )
2021-08-05 09:42:15 -04:00
Try {
Get-ChildItem -Path " ${LogsDirectory} \ ${LogFilePrefix} _*.*" -Filter '*.log' | `
Where-Object { $_ . LastWriteTime -lt $LogFileAllowedAge } | `
Remove-Item -Force -ErrorAction SilentlyContinue | Out-Null
}
Catch { Write-Output $_ . Exception . Message }
Try { If ( !( Test-Path $LogFile ) ) { New-Item -Path $LogFile -ItemType File -Force | Out-Null } }
Catch { Write-Output $_ . Exception . Message }
}
2021-05-27 18:07:02 -04:00
2023-10-05 13:11:38 -04:00
# Log a message to the log file
2023-10-05 13:39:07 -04:00
# IMPORTANT: In Powershell 'echo' is an alias for Write-Output, which is overriden when this Tools.ps1 file is called before your script.
# To prevent unwanted behavior, if you need to echo something do the following instead:
# Start-Process "cmd.exe" -ArgumentList '/C echo y | some-command'
2023-10-05 13:11:38 -04:00
Function Write-Output {
2020-11-06 11:24:16 -05:00
param ([ Parameter ( Mandatory = $true , ValueFromPipeline = $true )][ string ] $Message )
2023-10-05 14:09:27 -04:00
Add-Content -Path $Global:LogFile -Value " $( Get-Date -Format "yyyy-MM-dd HH:mm:ss" ) - ${Message} "
Microsoft . Powershell . Utility \ Write-Output $Message
2020-09-09 21:06:44 -04:00
}
2020-10-22 14:01:35 -04:00
2023-10-05 13:11:38 -04:00
# Log an error to the log file
Function Write-Error {
2020-11-06 11:24:16 -05:00
param ([ Parameter ( Mandatory = $true , ValueFromPipeline = $true )][ string ] $Message )
2023-10-05 14:09:27 -04:00
Add-Content -Path $Global:LogFile -Value " $( Get-Date -Format "yyyy-MM-dd HH:mm:ss" ) - Error: ${Message} "
Microsoft . Powershell . Utility \ Write-Error $Message
2020-09-07 13:43:37 -04:00
}
2023-10-05 13:11:38 -04:00
# Function to determine if the current user context is an Administrator
2020-11-06 11:24:16 -05:00
Function IsAdmin {
If ( ([ Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity ]:: GetCurrent ()). IsInRole ([ Security.Principal.WindowsBuiltInRole ]:: Administrator ) )
{ Return $true } Else { Return $false }
2020-09-07 13:43:37 -04:00
}
2023-10-05 13:11:38 -04:00
# Run a script from the live repo or from a local path
2020-11-06 11:24:16 -05:00
Function Run-Script {
param (
2023-10-05 13:11:38 -04:00
# Parameters for live-ps scripts
2020-11-06 11:24:16 -05:00
[ Parameter ( Mandatory = $false , ParameterSetName = 'live-ps' )]
[ string ] $Type = 'management-scripts' ,
[ Parameter ( Mandatory = $true , ParameterSetName = 'live-ps' )]
[ string ] $LivePSScript ,
2023-10-05 13:11:38 -04:00
# Parameters for local scripts
2020-11-06 11:24:16 -05:00
[ Parameter ( Mandatory = $true , ParameterSetName = 'local' )]
[ string ] $Path ,
2023-10-05 13:11:38 -04:00
### $NoWait will prevent waiting for the specified script to finish running
2020-11-06 11:24:16 -05:00
[ Parameter ( Mandatory = $false , ParameterSetName = 'local' )]
[ switch ] $NoWait = $false ,
2023-10-05 13:11:38 -04:00
# Parameters for all scripts
2020-11-06 11:24:16 -05:00
[ Parameter ( Mandatory = $false , ParameterSetName = 'live-ps' )]
[ Parameter ( Mandatory = $false , ParameterSetName = 'local' )]
[ string ] $Arguments = 'null' ,
2023-10-05 13:11:38 -04:00
### $HaltIfRunning is a collection of app names which, if running, will prevent the script from running
2020-11-06 11:24:16 -05:00
[ Parameter ( Mandatory = $false , ParameterSetName = 'live-ps' )]
[ Parameter ( Mandatory = $false , ParameterSetName = 'local' )]
[ string[] ] $HaltIfRunning = 'null' ,
2023-10-05 13:11:38 -04:00
### $ForceClose will forcefully close all apps specified in $HaltIfRunning
2020-11-06 11:24:16 -05:00
[ Parameter ( Mandatory = $false , ParameterSetName = 'live-ps' )]
[ Parameter ( Mandatory = $false , ParameterSetName = 'local' )]
[ switch ] $ForceClose = $false
)
If ( $Arguments -eq "null" ) { $Arguments = $null }
If ( $HaltIfRunning -eq "null" ) { $HaltIfRunning = $null }
If ( $LivePSScript ) {
$URL = "https://dev.emberkom.com/emberkom/ ${Type} /raw/branch/master/ ${LivePSScript} .ps1"
2023-10-05 13:11:38 -04:00
If ( !( IsURLValid -URL $URL ) ) { Write-Output "The specified script does not exist: ${LivePSScript} " ; Exit }
2020-11-06 11:24:16 -05:00
}
2023-10-05 13:11:38 -04:00
If ( $Path ) { If ( !( Test-Path $Path ) ) { Write-Output "The specified script does not exist: "" ${Path} """ ; Exit } }
If ( ( $null -ne $HaltIfRunning ) -and ( $ForceClose ) -and ( !( IsAdmin ) ) ) { Write-Output "Cannot close dependent apps because this task does not have administrative privileges" ; Exit }
2020-11-06 11:24:16 -05:00
If ( $HaltIfRunning ) {
$Halt = $false
2023-10-05 13:11:38 -04:00
Write-Output "Checking for running instances of the following dependent apps:"
2020-11-06 11:24:16 -05:00
ForEach ( $name in $HaltIfRunning ) {
2023-08-23 14:50:47 -04:00
$RunningInstances = Get-Process | Where-Object { $_ . Name -like " ${name} " }
2020-11-06 11:24:16 -05:00
If ( $RunningInstances ) {
If ( $ForceClose -eq $true ) {
2023-10-05 13:11:38 -04:00
Try { $name | Stop-Process -Force ; Write-Output " "" ${name} "" (force closed)" }
Catch { Write-Error $_ . Exception . Message ; Exit }
2020-11-06 11:24:16 -05:00
}
2023-10-05 13:11:38 -04:00
Else { $Halt = $true ; Write-Output " "" ${name} "" (running)" }
2020-09-23 21:31:52 -04:00
}
2023-10-05 13:11:38 -04:00
Else { Write-Output " "" ${name} "" (not running)" }
2020-09-23 20:43:29 -04:00
}
2023-10-05 13:11:38 -04:00
If ( $Halt -eq $true ) { Write-Output "Dependent apps are currently in use and are preventing the specified script from running" ; Exit }
2020-09-23 15:24:26 -04:00
}
2020-11-06 11:24:16 -05:00
switch ( $PSCmdlet . ParameterSetName ) {
'live-ps' {
2023-10-05 13:11:38 -04:00
Write-Output "Retrieving : ${LivePSScript} .ps1"
2024-06-24 17:06:45 -04:00
If ( $null -eq $Arguments ) {
$ScriptFile = Download-File -URL $URL
Write-Output "Sourcing: ${LivePSScript} .ps1"
. $ScriptFile
If ( Test-Path $ScriptFile ) { Remove-Item $ScriptFile -Force -ErrorAction SilentlyContinue }
} Else {
Try { $ScriptBlock = [ Scriptblock ]:: Create (( New-Object Net . WebClient ). DownloadString ( $URL )) }
Catch { Write-Error $_ . Exception . Message ; Exit }
Write-Output "Executing: ${LivePSScript} .ps1 ${Arguments} "
Try { Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $Arguments }
Catch { Write-Error $_ . Exception . Message ; Exit }
}
2020-10-22 14:01:35 -04:00
}
2020-11-06 11:24:16 -05:00
'local' {
2023-10-05 13:11:38 -04:00
Write-Output "Executing: ${Path} ${Arguments} "
2020-11-06 11:24:16 -05:00
If ( $NoWait ) {
Try { Start-Process " ${Path} " -ArgumentList "{ $Arguments }" }
2023-10-05 13:11:38 -04:00
Catch { Write-Error $_ . Exception . Message }
2020-11-06 11:24:16 -05:00
}
Else {
2023-10-05 13:11:38 -04:00
Write-Output " Waiting for script to finish..."
2020-11-06 11:24:16 -05:00
Try { Start-Process " ${Path} " -ArgumentList "{ $Arguments }" -Wait }
2023-10-05 13:11:38 -04:00
Catch { Write-Error $_ . Exception . Message }
Write-Output " Finished"
2020-11-06 11:24:16 -05:00
}
2020-09-23 15:24:26 -04:00
}
}
}
2023-10-05 13:11:38 -04:00
# Return a Powershell version object from a string
2020-11-06 11:24:16 -05:00
Function Get-Version {
param ([ Parameter ( Mandatory = $true )][ string ] $Version )
[ int ] $Sets = 4
$VersionArray = $Version . Split ( '.' )
If ( $VersionArray . Count -le $Sets ) { $Sets = $VersionArray . Count }
For ( $i = 0 ; $i -le ( $Sets - 1 ); $i ++) {
$Return = '{0}.{1}' -f $Return , $VersionArray [ $i ] }
Return [ version ] $Return . Trim ( '.' )
2020-09-07 20:18:06 -04:00
}
2023-10-05 13:11:38 -04:00
# Determines whether a URL is valid
2020-11-06 11:24:16 -05:00
Function IsURLValid {
param ([ Parameter ( Mandatory = $true , ValueFromPipeline = $true )][ string ] $URL )
2023-10-12 22:00:22 -04:00
Try { $status = [ System.Net.WebRequest ]:: Create ( $URL ). GetResponse (). StatusCode }
Catch { Write-Error $_ . Exception . Message ; Return $false }
If ( $status -eq 404 ) { Return $false }
Return $true
}
Function IsURL {
param ([ Parameter ( Mandatory = $true , ValueFromPipeline = $true )][ string ] $Value )
Return $value -match "https?:\/\/"
2020-09-23 21:52:25 -04:00
}
2023-10-05 13:11:38 -04:00
# Downloads a file from a URL
2020-11-06 11:24:16 -05:00
Function Download-File {
param (
[ Parameter ( Mandatory = $true , ValueFromPipeline = $true )][ string ] $URL ,
[ Parameter ( Mandatory = $false , ValueFromPipeline = $false )][ string ] $File
)
2023-10-12 22:00:22 -04:00
$URI = Get-AbsoluteURI -URL $URL
If ( $null -eq $URI ) { Return }
2023-11-01 15:36:58 -04:00
If ( [ string ]:: IsNullOrEmpty ( $File ) ) { $File = '{0}{1}' -f ( Get-TempPath ), ( Split-Path $URI -Leaf ) }
2023-11-01 15:47:27 -04:00
#Try { (New-Object System.Net.WebClient).DownloadFile($URI,$File) }
Try { Start-BitsTransfer -Source $URI -Destination $File }
2023-10-12 22:00:22 -04:00
Catch { Write-Error $_ . Exception . Message ; Return }
2023-11-01 15:36:58 -04:00
Return $File
2020-10-22 14:01:35 -04:00
}
2023-10-05 13:11:38 -04:00
# Install a program from a provided path
2020-11-06 11:24:16 -05:00
Function Install-Program {
param (
[ Parameter ( Mandatory = $true , ValueFromPipeline = $true )][ string ] $Path ,
[ Parameter ( Mandatory = $false )][ string ] $Arguments = '' ,
[ Parameter ( Mandatory = $false )][ switch ] $Delete = $false
)
If ( Test-Path " ${Path} " ) {
2023-10-05 13:11:38 -04:00
Write-Output "Running installer: "" ${Path} "" ${Arguments} "
2020-12-03 17:27:53 -05:00
Try {
If ( $Arguments -eq '' ) { Start-Process " ${Path} " -Wait }
Else { Start-Process " ${Path} " -ArgumentList " ${Arguments} " -Wait }
}
2023-10-05 13:11:38 -04:00
Catch { Write-Error $_ . Exception . Message }
Write-Output "Installer finished"
2020-11-06 11:24:16 -05:00
If ( $Delete ) {
2023-10-05 13:11:38 -04:00
Write-Output "Deleting installer: "" ${Path} """
2020-11-06 11:24:16 -05:00
Try { Remove-Item -Path " ${Path} " -Force }
2023-10-05 13:11:38 -04:00
Catch { Write-Error $_ . Exception . Message }
2020-11-06 11:24:16 -05:00
}
2020-10-22 14:01:35 -04:00
}
2023-10-05 13:11:38 -04:00
Else { Write-Output "The specified file could not be found: "" ${Path} """ }
2020-10-19 11:24:07 -04:00
}
2023-10-12 22:11:17 -04:00
# Function to silently install an MSI package either locally or after downloading it from a URL
Function Install-MSI {
param (
[ Parameter ( Mandatory = $true , ValueFromPipeline = $true )][ string ] $Path ,
[ Parameter ( Mandatory = $false )][ System.Collections.ArrayList ] $Properties = @ (),
[ Parameter ( Mandatory = $false )][ string ] $Log = $Global:LogFile ,
[ Parameter ( Mandatory = $false )][ switch ] $DeleteMSI = $false ,
[ Parameter ( Mandatory = $false )][ int ] $MaxRunTimeSeconds = 594
)
# If $Path is a URL, then try to download the file
If ( IsURL -Value $Path ) { $Path = Download-File -URL $Path }
# Make sure $FilePath exists
If ( !( Test-Path $Path ) ) { Write-Error "The specified file doesn't exist: "" ${Path} """ ; Return }
# Make sure $MaxRuntimeMinutes is a positive integer
If ( $MaxRunTimeSeconds -lt 0 ) {
$MaxRunTimeSeconds = 594
Write-Output "The specified maximum run time is invalid and has been changed to ${MaxRunTimeSeconds} seconds"
}
# Build the install command
[ System.Collections.ArrayList ] $MSIParameters = @ ( '/i' , """ ${Path} """ )
If ( ![ string ]:: IsNullOrEmpty ( $Log ) ) {
If ( Test-Path $Log ) { $MSIParameters . Add ( '/l+!*' ) | Out-Null } Else { $MSIParameters . Add ( '/l!*' ) | Out-Null }
$MSIParameters . Add ( """ ${Log} """ ) | Out-Null
}
If ( ![ string ]:: IsNullOrEmpty ( $Properties ) ) { $MSIParameters . Add ( $Properties ) | Out-Null }
# Run a job to install the MSI
Write-Output "Waiting ${MaxRunTimeSeconds} seconds for the following command: msiexec.exe ${MSIParameters} "
$InstallMSI = Start-Job -ScriptBlock { PROCESS { msiexec . exe $_ } } -InputObject $MSIParameters
# Wait for the job to end or the timeout to be reached
$endTime = ( Get-Date ). AddSeconds ( $MaxRunTimeSeconds )
While ( $ ( Get-Date ) -le $endTime ) {
If ( ( Get-Job -Name $InstallMSI . Name ). JobStateInfo . State -eq "Completed" ) { Break }
Start-Sleep -Seconds 1
}
# Stop the installation if it's still running
If ( ( Get-Job -Name $InstallMSI . Name ). JobStateInfo . State -ne "Completed" ) {
Write-Output "Timeout reached, stopping installation process"
Stop-Job -Job $InstallMSI
}
# Clean up the job
$result = Receive-Job -Job $InstallMSI
Remove-Job -Job $InstallMSI -Force
If ( [ string ]:: IsNullOrEmpty ( $result ) ) { Write-Output "Installation finished!" } Else { Write-Output "Installation finished with the following output: `n ${result} " }
# Delete the MSI
If ( $DeleteMSI ) {
Write-Output "Deleting "" ${Path} """
Try { Remove-Item -Path $Path -Force -ErrorAction SilentlyContinue }
Catch { Write-Error $_ . Exception . Message }
}
}
2023-10-05 13:11:38 -04:00
# Compare current Windows version with a supplied version
# The value supplied must be a string and must include at least 1 decimal
2020-11-06 11:24:16 -05:00
Function IsWindowsVersion {
param (
[ Parameter ( Mandatory = $true , ParameterSetName = 'gt' )][ string ] $gt ,
[ Parameter ( Mandatory = $true , ParameterSetName = 'ge' )][ string ] $ge ,
[ Parameter ( Mandatory = $true , ParameterSetName = 'lt' )][ string ] $lt ,
[ Parameter ( Mandatory = $true , ParameterSetName = 'le' )][ string ] $le ,
[ Parameter ( Mandatory = $true , ParameterSetName = 'eq' )][ string ] $eq
)
[ version ] $WindowsVersion = [ System.Environment ]:: OSVersion . Version
switch ( $PSCmdlet . ParameterSetName ) {
'gt' { If ( $WindowsVersion -gt ( Get-Version $gt ) ) { Return $true } Else { Return $false } }
'ge' { If ( $WindowsVersion -ge ( Get-Version $ge ) ) { Return $true } Else { Return $false } }
'lt' { If ( $WindowsVersion -lt ( Get-Version $lt ) ) { Return $true } Else { Return $false } }
'le' { If ( $WindowsVersion -le ( Get-Version $le ) ) { Return $true } Else { Return $false } }
'eq' { If ( $WindowsVersion -eq ( Get-Version $eq ) ) { Return $true } Else { Return $false } }
}
2020-09-09 16:46:21 -04:00
}
2023-10-05 13:11:38 -04:00
# Compare current Windows build with a supplied version
# The value supplied must be an int
2020-11-06 11:24:16 -05:00
Function IsWindowsBuild {
param (
[ Parameter ( Mandatory = $true , ParameterSetName = 'gt' )][ int ] $gt ,
[ Parameter ( Mandatory = $true , ParameterSetName = 'ge' )][ int ] $ge ,
[ Parameter ( Mandatory = $true , ParameterSetName = 'lt' )][ int ] $lt ,
[ Parameter ( Mandatory = $true , ParameterSetName = 'le' )][ int ] $le ,
[ Parameter ( Mandatory = $true , ParameterSetName = 'eq' )][ int ] $eq
)
[ int ] $WindowsBuild = [ System.Environment ]:: OSVersion . Version . Build
switch ( $PSCmdlet . ParameterSetName ) {
'gt' { If ( $WindowsBuild -gt $gt ) { Return $true } Else { Return $false } }
'ge' { If ( $WindowsBuild -ge $ge ) { Return $true } Else { Return $false } }
'lt' { If ( $WindowsBuild -lt $lt ) { Return $true } Else { Return $false } }
'le' { If ( $WindowsBuild -le $le ) { Return $true } Else { Return $false } }
'eq' { If ( $WindowsBuild -eq $eq ) { Return $true } Else { Return $false } }
}
2020-09-25 13:13:03 -04:00
}
2023-10-05 13:11:38 -04:00
# Get the path to the TEMP folder (context dependent)
2023-10-05 12:47:08 -04:00
Function Get-TempPath { Return [ System.IO.Path ]:: GetTempPath () }
2020-10-22 14:01:35 -04:00
2023-10-05 14:22:37 -04:00
# Get a random file name with random extension
2023-10-06 13:04:09 -04:00
Function Get-RandomFileName { Return [ System.IO.Path ]:: GetRandomFileName () }
2023-10-05 14:22:37 -04:00
2023-10-06 13:04:09 -04:00
# Create a new temp file in the current TEMP folder
Function New-TemporaryFile {
$file = [ System.IO.Path ]:: Combine ( $ ( Get-TempPath ), $ ( Get-RandomFileName ))
New-Item -Path $file -ItemType File -Force -ErrorAction SilentlyContinue | Out-Null
Return Get-Item -Path $file
}
# Create a new temp directory in the current TEMP folder
Function New-TemporaryDirectory {
$directory = [ System.IO.Path ]:: Combine ( $ ( Get-TempPath ), $ ( Get-RandomFileName ))
New-Item -Path $directory -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
Return Get-Item -Path $directory
}
2023-10-05 14:22:37 -04:00
2023-10-05 13:11:38 -04:00
# Determine if the system is 64-bit or not
2020-11-06 11:24:16 -05:00
Function Is64bit {
switch ( $ ( Get-CimInstance -ClassName Win32_OperatingSystem -Property OSArchitecture | Select-Object -ExpandProperty OSArchitecture ) )
{ '64-bit' { Return $true } '32-bit' { Return $false } }
2020-09-25 13:13:03 -04:00
}
2023-10-05 13:11:38 -04:00
# Determine if the Powershell process is 64-bit or not
2020-11-06 11:24:16 -05:00
Function Is64bitShell { If ( [ System.Environment ]:: Is64BitProcess ) { Return $true } Else { Return $false } }
2020-10-19 11:24:07 -04:00
2023-10-05 13:11:38 -04:00
# Determine if a user is in a built-in role or specified group
2020-10-22 14:01:35 -04:00
Function IsMemberOf {
2020-11-06 11:24:16 -05:00
param (
[ Parameter ( Mandatory = $false )]
[ switch ] $Builtin = $false ,
[ Parameter ( Mandatory = $true , ValueFromPipeline = $true )]
[ string ] $Group
)
If ( $Builtin ) {
Switch ( $Group ) {
( "Administrator" -or "Administrators" )
{ $Group = [ System.Security.Principal.WindowsBuiltInRole ]:: Administrator ; Break }
( "Backup Operator" -or "Backup Operators" -or "BackupOperator" -or "BackupOperators" )
{ $Group = [ System.Security.Principal.WindowsBuiltInRole ]:: BackupOperator ; Break }
( "System Operator" -or "System Operators" -or "SystemOperator" -or "SystemOperators" )
{ $Group = [ System.Security.Principal.WindowsBuiltInRole ]:: SystemOperator ; Break }
( "Account Operator" -or "Account Operators" -or "AccountOperator" -or "AccountOperators" )
{ $Group = [ System.Security.Principal.WindowsBuiltInRole ]:: AccountOperator ; Break }
( "Print Operator" -or "Print Operators" -or "PrintOperator" -or "PrintOperators" )
{ $Group = [ System.Security.Principal.WindowsBuiltInRole ]:: PrintOperator ; Break }
( "Replicator" -or "Replicators" )
{ $Group = [ System.Security.Principal.WindowsBuiltInRole ]:: Replicator ; Break }
( "Power User" -or "Power Users" -or "PowerUser" -or "PowerUsers" )
{ $Group = [ System.Security.Principal.WindowsBuiltInRole ]:: PowerUser ; Break }
( "User" -or "Users" )
{ $Group = [ System.Security.Principal.WindowsBuiltInRole ]:: User ; Break }
( "Guest" -or "Guests" )
{ $Group = [ System.Security.Principal.WindowsBuiltInRole ]:: Guest ; Break }
default { ; Break }
}
2020-10-22 14:01:35 -04:00
}
2020-11-06 11:24:16 -05:00
Return ([ Security.Principal.WindowsPrincipal][System.Security.Principal.WindowsIdentity ]:: GetCurrent ()). IsInRole ( $Group )
2020-10-22 14:01:35 -04:00
}
2020-11-06 11:24:16 -05:00
2023-10-05 13:11:38 -04:00
# Start, stop, restart, or delete a list of services
2020-11-06 11:24:16 -05:00
Function Control-Service
{
param (
[ Parameter ( Mandatory = $true , ParameterSetName = 'start' )]
[ switch ] $Start ,
[ Parameter ( Mandatory = $true , ParameterSetName = 'stop' )]
[ switch ] $Stop ,
[ Parameter ( Mandatory = $true , ParameterSetName = 'restart' )]
[ switch ] $Restart ,
2021-07-29 12:57:11 -04:00
[ Parameter ( Mandatory = $true , ParameterSetName = 'delete' )]
[ switch ] $Delete ,
2021-10-27 13:11:19 -04:00
[ Parameter ( Mandatory = $true , ParameterSetName = 'disable' )]
[ switch ] $Disable ,
2020-11-06 11:24:16 -05:00
[ Parameter ( Mandatory = $true , ValueFromPipeline = $true , ParameterSetName = 'start' )]
[ Parameter ( Mandatory = $true , ValueFromPipeline = $true , ParameterSetName = 'stop' )]
[ Parameter ( Mandatory = $true , ValueFromPipeline = $true , ParameterSetName = 'restart' )]
2021-07-29 12:57:11 -04:00
[ Parameter ( Mandatory = $true , ValueFromPipeline = $true , ParameterSetName = 'delete' )]
2021-10-27 13:11:19 -04:00
[ Parameter ( Mandatory = $true , ValueFromPipeline = $true , ParameterSetName = 'disable' )]
2020-11-06 11:24:16 -05:00
[ string[] ] $Name ,
[ Parameter ( Mandatory = $false , ParameterSetName = 'start' )]
[ Parameter ( Mandatory = $false , ParameterSetName = 'stop' )]
[ Parameter ( Mandatory = $false , ParameterSetName = 'restart' )]
2021-07-29 12:57:11 -04:00
[ Parameter ( Mandatory = $false , ParameterSetName = 'delete' )]
2021-10-27 13:11:19 -04:00
[ Parameter ( Mandatory = $false , ParameterSetName = 'disable' )]
2020-11-06 11:24:16 -05:00
[ switch ] $Match = $false
)
switch ( $PSCmdlet . ParameterSetName ) {
'start' {
$statuscondition = 'Running'
$verb = 'start'
$verbing = 'Starting'
}
'stop' {
$statuscondition = 'Stopped'
$verb = 'stop'
$verbing = 'Stopping'
}
'restart' {
$ArgString = $MyInvocation . Line
$StopCmd = $ArgString -replace ' -Restart ' , ' -Stop '
$StartCmd = $ArgString -replace ' -Restart ' , ' -Start '
Invoke-Expression $StopCmd
Invoke-Expression $StartCmd
Exit
}
2021-07-29 12:57:11 -04:00
'delete' {
2023-10-05 13:11:38 -04:00
# Delete the service
2021-07-29 12:57:11 -04:00
}
2021-10-27 13:11:19 -04:00
'disable' {
$ArgString = $MyInvocation . Line
$StopCmd = $ArgString -replace ' -Disable ' , ' -Stop '
Invoke-Expression $StopCmd
$statuscondition = 'Disabled'
$verb = 'disable'
$verbing = 'Disabling'
}
2020-11-06 11:24:16 -05:00
}
ForEach ( $item in $Name ) {
$service = Get-Service -Name $item -ErrorAction SilentlyContinue
If ( !( $service ) ) {
2023-10-05 13:11:38 -04:00
Write-Output "Cannot find the service ' ${item} '"
2020-11-06 11:24:16 -05:00
If ( $Match ) {
2023-10-05 13:11:38 -04:00
Write-Output "Attempting to match ' ${item} ' to service display names"
2021-10-27 13:11:19 -04:00
ForEach ( $svc in (( Get-Service | Where-Object { $_ . DisplayName -match $item }). Name ) ) {
2020-11-06 11:24:16 -05:00
Switch ( $verb ) {
'start' { Control-Service -Start -Name $svc }
'stop' { Control-Service -Stop -Name $svc }
2021-10-27 13:11:19 -04:00
'disable' { Control-Service -Disable -Name $svc }
2020-11-06 11:24:16 -05:00
}
}
}
}
Else {
$name = $service . Name
$fullname = $service . DisplayName
2021-10-27 13:11:19 -04:00
If ( ([ string ]:: IsNullOrEmpty ( $fullname )) -or ( $fullname -eq $name ) ) {
2020-11-06 11:24:16 -05:00
$displayname = "' ${name} '"
}
Else {
$displayname = """ ${fullname} "" ( ${name} )"
}
If ( $service . Status -ne $statuscondition ) {
2023-10-05 13:11:38 -04:00
Write-Output " ${verbing} ${displayname} "
2021-10-27 13:11:19 -04:00
Try {
Switch ( $verb ) {
'start' { Start-Service -Name $name }
'stop' { Stop-Service -Name $name -Force }
'disable' { Set-Service -Name " ${name} " -StartupType Disabled }
2020-11-06 11:24:16 -05:00
}
}
2023-10-05 13:11:38 -04:00
Catch { Write-Error $_ . Exception . Message }
2020-11-06 11:24:16 -05:00
}
}
}
}
2023-10-05 13:11:38 -04:00
# Stop a process
2020-11-06 11:24:16 -05:00
Function End-Process
{
param (
[ Parameter ( Mandatory = $true , ValueFromPipeline = $true )][ string[] ] $Name ,
[ Parameter ( Mandatory = $false )][ switch ] $Match = $false ,
2021-07-30 09:42:54 -04:00
[ Parameter ( Mandatory = $false )][ switch ] $Force = $false
2020-11-06 11:24:16 -05:00
)
ForEach ( $item in $Name ) {
$process = Get-Process -Name $item -ErrorAction SilentlyContinue
If ( !( $process ) ) {
2023-10-05 13:11:38 -04:00
Write-Output "Cannot find the process ' ${item} '"
2020-11-06 11:24:16 -05:00
If ( $Match ) {
2023-10-05 13:11:38 -04:00
Write-Output "Attempting to match ' ${item} ' to all running process names"
2023-08-23 14:50:47 -04:00
ForEach ( $proc in (( Get-Process | Where-Object { $_ . Name -match $item }). Name ) ) {
2020-11-06 11:24:16 -05:00
If ( $Force ) { End-Process -Name $proc -Force } Else { End-Process -Name $proc }
}
}
}
Else {
$name = $process . Name
If ( $Force ) {
2023-10-05 13:11:38 -04:00
Write-Output "Forcefully stopping ' ${name} ' process"
2020-11-06 11:24:16 -05:00
Stop-Process $process -Force -ErrorAction SilentlyContinue
}
Else {
2023-10-05 13:11:38 -04:00
Write-Output "Attempting to gracefully close ' ${name} ' process"
2020-11-06 11:24:16 -05:00
$process . CloseMainWindow () | Out-Null
}
}
}
2020-10-22 14:01:35 -04:00
}
2020-12-03 18:16:55 -05:00
2023-10-06 13:04:09 -04:00
Function New-Shortcut {
2020-12-03 18:16:55 -05:00
param (
[ Parameter ( Mandatory = $true )][ string ] $Path ,
[ Parameter ( Mandatory = $true )][ string ] $TargetPath ,
[ Parameter ( Mandatory = $false )][ string ] $Arguments ,
[ Parameter ( Mandatory = $false )][ string ] $Description ,
[ Parameter ( Mandatory = $false )][ string ] $Icon = '' ,
[ Parameter ( Mandatory = $false )][ string ] $WorkingDirectory = ''
)
If ( Test-Path $TargetPath ) {
If ( Test-Path $Path ) {
2023-10-05 13:11:38 -04:00
Write-Output "Deleting existing shortcut at "" ${Path} """
2020-12-03 18:16:55 -05:00
Try { Remove-Item $Path -Force -ErrorAction SilentlyContinue }
2023-10-05 13:11:38 -04:00
Catch { Write-Error $_ . Exception . Message }
2020-12-03 18:16:55 -05:00
}
If ( $WorkingDirectory -eq '' ) { $WorkingDirectory = Split-Path $TargetPath -Parent }
If ( $Icon -eq '' ) { $Icon = " ${TargetPath} , 0" }
$WshShell = New-Object -ComObject WScript . Shell
$Shortcut = $WshShell . CreateShortcut ( $Path )
$Shortcut . Arguments = $Arguments
$Shortcut . Description = $Description
$Shortcut . IconLocation = $Icon
$Shortcut . WorkingDirectory = $WorkingDirectory
$Shortcut . TargetPath = $TargetPath
2023-10-05 13:11:38 -04:00
Write-Output "Creating shortcut to "" ${TargetPath} "" at "" ${Path} """
2020-12-03 18:16:55 -05:00
Try { $Shortcut . Save () }
2023-10-05 13:11:38 -04:00
Catch { Write-Error $_ . Exception . Message }
2020-12-03 18:16:55 -05:00
}
2023-10-05 13:11:38 -04:00
Else { Write-Output "Cannot create shortcut because the target path does not exist" }
2021-05-24 14:05:59 -04:00
}
2023-10-06 13:04:09 -04:00
# Enable a Windows Event Log
Function Enable-Log {
param ([ Parameter ( Mandatory = $true , ValueFromPipeline = $true )][ string ] $Name )
2021-05-24 14:05:59 -04:00
Try {
2023-10-06 13:04:09 -04:00
$log = New-Object System . Diagnostics . Eventing . Reader . EventLogConfiguration $Name
Write-Output "Enabing ${Name} event log"
$log . IsEnabled = $true
$log . SaveChanges ()
2021-05-24 14:05:59 -04:00
}
2023-10-06 13:04:09 -04:00
Catch { Write-Error $_ . Exception . Message }
2021-08-05 09:42:15 -04:00
}
2023-10-06 13:04:09 -04:00
# Disable a Windows Event Log
Function Disable-Log {
param ([ Parameter ( Mandatory = $true , ValueFromPipeline = $true )][ string ] $Name )
2021-09-23 13:14:16 -04:00
Try {
2023-10-06 13:04:09 -04:00
$log = New-Object System . Diagnostics . Eventing . Reader . EventLogConfiguration $Name
Write-Output "Disabing ${Name} event log"
$log . IsEnabled = $false
$log . SaveChanges ()
2021-09-23 13:14:16 -04:00
}
2023-10-05 13:11:38 -04:00
Catch { Write-Error $_ . Exception . Message }
2021-09-23 13:14:16 -04:00
}
# Function to install the MSP360 Powershell module
2023-12-07 12:06:28 -05:00
Function Import-MSP360Module {
2021-09-23 13:14:16 -04:00
Try {
2026-05-29 11:24:53 -04:00
$Module = Get-Module -ListAvailable -Name MSP360 , msp360 | Sort-Object Version -Descending | Select-Object -First 1
If ( $Module ) { Import-Module -Name $Module . Name -Force -ErrorAction Stop ; Return }
2023-12-07 12:06:28 -05:00
If ( -not ( IsAdmin ) ) { Write-Output "The MSP360 Powershell module needs to be installed, but the current account is not an administrative user" ; Return }
Write-Output "Installing MSP360 Powershell module"
2026-05-29 11:24:53 -04:00
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope Process -Force
2023-12-07 12:06:28 -05:00
[ Net.ServicePointManager ]:: SecurityProtocol = [ Net.SecurityProtocolType ]:: Tls12
2026-05-18 16:38:50 -04:00
Invoke-Expression ( New-Object System . Net . WebClient ). DownloadString ( 'https://raw.githubusercontent.com/msp360/MSP360-PSModule/master/private/Install-MSP360Module.ps1' )
2023-12-07 12:06:28 -05:00
Install-MSP360Module
2026-05-29 11:24:53 -04:00
Import-Module -Name MSP360 -Force -ErrorAction Stop
2021-09-23 13:14:16 -04:00
}
2026-05-29 11:24:53 -04:00
Catch { Write-Error "Could not import or install MSP360 PowerShell module: $( $_ . Exception . Message ) " }
2021-09-23 13:14:16 -04:00
}
2023-10-06 13:04:09 -04:00
# Function to ZIP a file or directory, returns the newly created zip file
2023-10-06 16:06:59 -04:00
# TODO: updating an archive with files that already exist inside will create duplicates and cause problems during extraction
2023-10-06 13:04:09 -04:00
Function Compress-Archive {
2021-11-12 11:09:52 -05:00
param (
2023-10-06 16:06:59 -04:00
[ Parameter ( ValueFromPipeline = $true , Mandatory = $true )][ string ] $Path ,
[ Parameter ( ValueFromPipeline = $false , Mandatory = $false )][ string ] $DestinationPath ,
[ Parameter ( ValueFromPipeline = $false , Mandatory = $false )][ switch ] $Force = $false
2021-11-12 11:09:52 -05:00
)
2023-10-06 13:04:09 -04:00
BEGIN {
2023-10-06 16:06:59 -04:00
If ( !( Test-Path $Path ) ) { Write-Error "The specified path does not exist: "" ${Path} """ ; Exit }
If ( [ string ]:: IsNullOrEmpty ( $DestinationPath ) ) {
Switch ( Get-Item -Path $Path ) {
{ $_ -is [ System.IO.DirectoryInfo ]} { $DestinationPath = '{0}.zip' -f ( $Path . TrimEnd ( '\' ))}
{ $_ -is [ System.IO.FileInfo ]} { $DestinationPath = '{0}\{1}.zip' -f ( Split-Path $Path -Parent ), ([ System.IO.Path ]:: GetFileNameWithoutExtension ( $Path )) }
}
}
2023-10-06 13:04:09 -04:00
If ( ( $Force ) -and ( Test-Path $DestinationPath ) ) {
Write-Output "Deleting existing archive: "" ${DestinationPath} """
Remove-Item -Path $DestinationPath -Force -ErrorAction Stop
2021-11-10 20:18:43 -05:00
}
2023-10-06 13:04:09 -04:00
}
PROCESS {
If ( !( $Force ) -and ( Test-Path $DestinationPath ) ) { $CreateNewArchive = $false } Else { $CreateNewArchive = $true }
Try {
Add-Type -Assembly "System.IO.Compression"
Add-Type -Assembly "System.IO.Compression.FileSystem"
Switch ( $ ( Get-Item -Path $Path ) ) {
{ $_ -is [ System.IO.DirectoryInfo ]} {
If ( $CreateNewArchive ) {
Write-Output "Creating new zip archive: "" ${DestinationPath} """
[ System.IO.Compression.ZipFile ]:: CreateFromDirectory ( $Path , $DestinationPath , [ System.IO.Compression.CompressionLevel ]:: Optimal , $true )
} Else {
If ( ( Get-Command Microsoft . Powershell . Archive \ Compress-Archive -ErrorAction SilentlyContinue ) ) {
2023-10-06 16:06:59 -04:00
Write-Output "Calling Microsoft util"
2023-10-06 13:04:09 -04:00
Microsoft . Powershell . Archive \ Compress-Archive -Path $Path -DestinationPath $DestinationPath -Update -CompressionLevel Optimal
} Else {
Write-Error "Updating an existing zip archive with a folder structure is not currently supported"
}
}
}
{ $_ -is [ System.IO.FileInfo ]} {
If ( $CreateNewArchive ) {
Write-Output "Creating new zip archive: "" ${DestinationPath} """
$ArchiveMode = [ System.IO.Compression.ZipArchiveMode ]:: Create
} Else { $ArchiveMode = [ System.IO.Compression.ZipArchiveMode ]:: Update }
[ System.IO.Compression.ZipArchive ] $DestinationArchive = [ System.IO.Compression.ZipFile ]:: Open ( $DestinationPath , $ArchiveMode )
[ System.IO.Compression.ZipFileExtensions ]:: CreateEntryFromFile ( $DestinationArchive , $Path , ( Split-Path $Path -Leaf )) | Out-Null
}
2021-11-10 20:18:43 -05:00
}
}
2023-10-06 13:04:09 -04:00
Catch { Write-Error $_ . Exception . Message }
2023-10-06 16:06:59 -04:00
Finally { If ( $DestinationArchive ) { $DestinationArchive . Dispose () } }
2023-10-06 13:04:09 -04:00
}
END {
If ( ! $CreateNewArchive ) { Write-Output "Finished updating existing archive: "" ${DestinationPath} """ }
If ( Test-Path $DestinationPath ) { Return Get-Item -Path $DestinationPath } Else { Return $null }
}
2021-11-10 20:18:43 -05:00
}
2023-10-06 16:06:59 -04:00
# Function to unzip a file or directory
# TODO: extracting an archive will fail if the destination file(s) already exists
Function Expand-Archive {
param (
[ Parameter ( ValueFromPipeline = $true , Mandatory = $true )][ string ] $Path ,
[ Parameter ( ValueFromPipeline = $false , Mandatory = $false )][ string ] $DestinationPath ,
[ Parameter ( ValueFromPipeline = $false , Mandatory = $false )][ switch ] $Force = $false
)
If ( !( Test-Path $Path ) ) { Write-Output "The specified zip archive does not exist: "" ${Path} """ ; Exit }
If ( [ System.IO.Path ]:: GetExtension ( $Path ) -ne ".zip" ) { Write-Output "The path specified is not a zip file: "" ${Path} """ ; Exit }
If ( [ string ]:: IsNullOrEmpty ( $DestinationPath ) ) { $DestinationPath = Split-Path -Parent $Path }
Try {
2023-11-01 15:51:24 -04:00
Add-Type -Assembly "System.IO.Compression"
2023-10-06 16:06:59 -04:00
Add-Type -Assembly "System.IO.Compression.FileSystem"
Write-Output "Extracting "" ${Path} "" to "" ${DestinationPath} """
[ System.IO.Compression.ZipArchive ] $SourceArchive = [ System.IO.Compression.ZipFile ]:: Open ( $Path , [ System.IO.Compression.ZipArchiveMode ]:: Read )
[ System.IO.Compression.ZipFileExtensions ]:: ExtractToDirectory ( $SourceArchive , $DestinationPath )
2021-11-10 20:18:43 -05:00
}
2023-10-06 16:06:59 -04:00
Catch { Write-Error $_ . Exception . Message }
Finally { If ( $SourceArchive ) { $SourceArchive . Dispose () } }
2021-11-10 20:18:43 -05:00
}
2021-11-18 21:03:03 -05:00
# Function to get a copy of the LiquidFiles CLI agent
2022-12-15 12:03:47 -05:00
Function Get-LiquidFilesCLI {
2021-11-18 21:03:03 -05:00
$url = 'https://lfupdate.s3.amazonaws.com/clients/windows_cli/LiquidFilesCLI-2.0.128.zip'
$zip_filename = Split-Path -Leaf $url
$zip_path = " ${Global:ToolsDirectory} \ ${zip_filename} "
$exe_path = '{0}\{1}.exe' -f $Global:ToolsDirectory ,[ System.IO.Path ]:: GetFileNameWithoutExtension ( $zip_filename )
If ( Test-Path $zip_path ) {
2023-10-05 13:11:38 -04:00
Write-Output "Deleting existing zip archive: ${zip_path} "
2021-11-18 21:03:03 -05:00
Try { Remove-Item -Path $zip_path -Force }
2023-10-05 13:11:38 -04:00
Catch { Write-Error $_ . Exception . Message }
2021-11-18 21:03:03 -05:00
}
If ( Test-Path $exe_path ) {
2023-10-05 13:11:38 -04:00
Write-Output "Deleting existing file: ${exe_path} "
2021-11-18 21:03:03 -05:00
Try { Remove-Item -Path $exe_path -Force }
2023-10-05 13:11:38 -04:00
Catch { Write-Error $_ . Exception . Message }
2021-11-18 21:03:03 -05:00
}
Download-File -URL $url -File $zip_path | Out-Null
2023-10-06 13:04:09 -04:00
Expand-Archive $zip_path
2021-11-18 21:03:03 -05:00
Return $exe_path
}
2022-02-03 09:20:26 -05:00
# Function to determine if an app is running
2022-12-15 12:03:47 -05:00
Function IsRunning ([ Parameter ( ValueFromPipeline = $true )][ string ] $Name ) {
2022-11-19 14:38:02 -05:00
$RunningInstances = Get-Process | Where-Object { $_ . Name -ilike $Name }
2022-02-03 09:20:26 -05:00
If ( $RunningInstances ) { Return $true } Else { Return $false }
}
2022-03-17 18:09:47 -04:00
# Function to create a local user account
2023-12-05 14:56:12 -05:00
Function New-LocalAccount {
2022-03-17 18:09:47 -04:00
param (
[ Parameter ( Mandatory = $true )][ string ] $Username ,
2025-10-08 16:12:56 -04:00
[ Parameter ( Mandatory = $true )][ string ] $Password ,
2022-03-17 18:09:47 -04:00
[ Parameter ( Mandatory = $true )][ string ] $FullName ,
[ Parameter ( Mandatory = $true )][ string ] $Description ,
[ Parameter ( Mandatory = $false )][ switch ] $MakeAdmin = $false ,
[ Parameter ( Mandatory = $false )][ switch ] $Hide = $false
)
2025-10-08 16:12:56 -04:00
$SecurePassword = ConvertTo-SecureString -String $Password -AsPlainText -Force
$Password = $null
2023-12-05 14:59:17 -05:00
If ( -not ( IsAdmin ) ) {
2023-12-05 14:56:12 -05:00
Write-Output "Cannot create or modify a local account without administrative privileges"
Return
}
If ( $ ( Get-CimInstance -ClassName Win32_OperatingSystem -Property ProductType | Select-Object -ExpandProperty ProductType ) -eq "2" ) {
Write-Output "Cannot create or modify local accounts on a domain controller"
Return
}
If ( $ ( Get-LocalUser -Name $Username -ErrorAction SilentlyContinue ) ) {
Write-Output "Updating user: ${Username} "
2026-05-18 16:38:50 -04:00
Try { Set-LocalUser -Name " ${Username} " -Password $SecurePassword -FullName " ${FullName} " -Description " ${Description} " -AccountNeverExpires -PasswordNeverExpires | Out-Null }
Catch { Write-Error "Could not update user `n " + $_ . Exception . Message ; Exit }
2023-12-05 14:56:12 -05:00
}
Else {
Write-Output "Creating user: ${Username} "
2026-05-18 16:38:50 -04:00
Try { New-LocalUser -Name " ${Username} " -Password $SecurePassword -FullName " ${FullName} " -Description " ${Description} " -AccountNeverExpires -PasswordNeverExpires | Out-Null }
Catch { Write-Error "Could not create user `n " + $_ . Exception . Message ; Exit }
2023-12-05 14:56:12 -05:00
}
If ( ( -not ( $ ( Get-LocalGroupMember -Group "Administrators" -Member $Username -ErrorAction SilentlyContinue ))) -and $MakeAdmin ) {
Write-Output "Adding "" ${Username} "" to local Administrators group"
Try { Add-LocalGroupMember -Name "Administrators" -Member $ ( Get-LocalUser -Name " ${Username} " ) }
2026-05-18 16:38:50 -04:00
Catch { Write-Error "Could not add user to Administrators group `n " + $_ . Exception . Message ; Exit }
2022-03-17 18:09:47 -04:00
}
2022-03-24 16:28:30 -04:00
If ( $Hide ) {
2025-10-08 16:12:56 -04:00
$RegPath = "HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList"
$HideUser = $true
If ( -not ( Test-Path $RegPath ) ) {
New-Item -Path $RegPath -Force | Out-Null
} Else {
$UserList = Get-ItemProperty -Path $RegPath
ForEach ( $User in $UserList . PSObject . Properties ) {
If ( $User . Name -ieq $Username ) {
Write-Output "The user "" ${Username} "" is already hidden from the login screen"
$HideUser = $false
Break
}
}
}
If ( $HideUser ) { New-ItemProperty -Path $RegPath -Name $Username -Value 0 -PropertyType DWord -Force | Out-Null }
2022-03-24 16:28:30 -04:00
}
}
# Returns the number of MB a process is consuming
2022-12-15 12:03:47 -05:00
Function Get-MemoryUsage ([ string ] $AppName ) {
2022-03-24 16:28:30 -04:00
[ math ]:: Round (( Get-Process -Name $AppName -ErrorAction SilentlyContinue | Measure-Object -Property WorkingSet -Sum ). Sum / 1 MB)
}
# Kills a process if it's using more than the specified amount of memory
2022-12-15 12:03:47 -05:00
Function Stop-MemoryHog ([ string ] $AppName ,[ int ] $MemoryLimit ) {
2022-03-24 16:28:30 -04:00
$MemoryUsed = ( Get-MemoryUsage -AppName $AppName )
If ( $MemoryUsed -gt $MemoryLimit ) {
$MemoryDifference = $MemoryUsed - $MemoryLimit
2023-10-05 13:11:38 -04:00
Write-Output "Killing "" ${AppName} "" for using ${MemoryDifference} MB over the limit of ${MemoryLimit} MB"
2022-03-24 16:28:30 -04:00
Try { Get-Process -Name $AppName -ErrorAction SilentlyContinue | Stop-Process -Force }
2023-10-05 13:11:38 -04:00
Catch { Write-Error $_ . Exception . Message }
2022-03-24 16:28:30 -04:00
}
2022-03-17 18:09:47 -04:00
}
2022-11-10 11:22:43 -05:00
# Returns a formatted list of the largest or smallest files in a directory
2022-12-15 12:03:47 -05:00
Function Get-FileSizes {
2022-11-10 11:22:43 -05:00
param (
[ Parameter ( ValueFromPipeline = $true )][ string ] $Path ,
[ switch ] $Recurse ,
[ switch ] $IncludeHidden ,
[ Parameter ( ParameterSetName = "largest" )][ int ] $Largest ,
[ Parameter ( ParameterSetName = "smallest" )][ int ] $Smallest ,
[ string ] $Units ,
[ int ] $Precision = 2
)
2024-08-08 15:36:26 -04:00
# Fix drive case so output looks nicer
Switch ( $Path . Length ) {
1 { $Path = $Path . ToUpper () }
{ $_ -gt 1 } {
$split = $Path . Split ( ":" )
$Path = $split [ 0 ]. ToUpper () + ":"
If ( $split [ 1 ] ) { $Path += $split [ 1 ] }
}
}
2022-11-10 11:22:43 -05:00
# If no path is specified, set $Path to the directory containing user profiles
If ( -not $Path ) {
2025-01-06 13:16:48 -05:00
$Path = ( Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' -Name ProfilesDirectory ). ProfilesDirectory
2022-11-10 11:22:43 -05:00
$Recurse = $true
$IncludeHidden = $false
}
2024-08-08 12:41:58 -04:00
# If the path is not an absolute path, make it so
ElseIf ( -not ( Test-Path $Path ) ) {
$NewPath = $Path [ 0 ] + ":\"
If ( -not ( Test-Path $NewPath ) ) {
Write-Error "Could not find path: ${Path} "
Return
}
$Path = $NewPath
}
2022-11-10 11:22:43 -05:00
# Change the sort order depending on which parameter set was specified
Switch ( $PSCmdlet . ParameterSetName ) {
"largest" { $SortOrder = $true ; $Quantity = $Largest }
"smallest" { $SortOrder = $false ; $Quantity = $Smallest }
}
# Set the units to show sizes in output
Switch ( $Units . ToUpper () ) {
"KB" { $UnitLabel = "Kilobytes (KB)" ; $Divisor = 1 KB }
"MB" { $UnitLabel = "Megabytes (MB)" ; $Divisor = 1 MB }
"GB" { $UnitLabel = "Gigabytes (GB)" ; $Divisor = 1 GB }
"TB" { $UnitLabel = "Terabytes (TB)" ; $Divisor = 1 TB }
default { $UnitLabel = "Bytes" ; $Divisor = 1 }
}
# Get the file paths and sizes as specified
2024-08-08 13:13:54 -04:00
$FileSizes = Get-ChildItem -LiteralPath "\\?\ ${Path} " -Recurse: $Recurse -Force: $IncludeHidden -ErrorAction SilentlyContinue | `
Where-Object { ! $_ . PSIsContainer -and ( $_ . FullName . Length -lt 260 )} | `
2024-05-09 17:00:20 -04:00
Sort-Object -Descending: $SortOrder -Property Length | `
2024-08-08 13:13:54 -04:00
Select-Object -First $Quantity @ { Name = "File Path" ; Expression ={[ IO.Path ]:: Combine ( $_ . DirectoryName , $_ . Name )}}, @ { Name = $UnitLabel ; Expression ={[ Math ]:: Round ( $_ . Length / $Divisor , $Precision )}}
2022-11-10 11:22:43 -05:00
# Set output prefix
$prefix = "The ${Quantity} " + $PSCmdlet . ParameterSetName + " files found at "" ${Path} """
# Output to console
$constr = " ${prefix} " + $ ( $FileSizes | Out-String )
2023-11-09 11:20:10 -05:00
Microsoft . Powershell . Utility \ Write-Output $constr
2022-11-10 11:22:43 -05:00
# Output to log file
$logstr = " ${prefix} " + $ ( $FileSizes | Format-List | Out-String )
2023-10-05 13:11:38 -04:00
Write-Output $logstr
2022-11-10 11:22:43 -05:00
}
2022-11-11 15:11:14 -05:00
# Function to write text to a file in UTF8 encoding without BOM
2022-12-15 12:03:47 -05:00
Function WriteToFile_UTF8NoBOM {
2022-11-11 15:11:14 -05:00
param (
[ Parameter ( ValueFromPipeline = $false )][ string ] $FilePath ,
[ Parameter ( ValueFromPipeline = $true )][ string ] $Value
)
$UTF8NoBOM = New-Object System . Text . UTF8Encoding $false
Try { [ IO.File ]:: WriteAllText ( $FilePath , $CONFIG , $UTF8NoBOM ) }
2023-10-05 13:11:38 -04:00
Catch { Write-Error $_ . Exception . Message }
2022-11-11 15:11:14 -05:00
}
2022-11-19 14:38:02 -05:00
# Function to enumerate all user profile folders
2023-10-06 16:14:34 -04:00
Function Get-UserProfiles {
2025-01-06 13:16:48 -05:00
$ProfilePath = ( Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' -Name ProfilesDirectory ). ProfilesDirectory
2022-11-19 14:38:02 -05:00
$UserProfilePaths = @ ()
ForEach ( $userProfile in ( Get-ChildItem -Path $ProfilePath | Where-Object { $_ . PSIsContainer }) ) {
$UserProfilePaths += , @ ( $userProfile . FullName )
}
Return $UserProfilePaths
}
2022-11-23 15:59:33 -05:00
# Function to test for the presense of a Registry property
2022-12-15 12:03:47 -05:00
Function Test-RegistryProperty {
2022-11-23 15:59:33 -05:00
param (
[ Parameter ( Mandatory = $true )][ string ] $Path ,
[ Parameter ( Mandatory = $true )][ string ] $Name
)
Try {
Get-ItemProperty -Path $Path | Select-Object -ExpandProperty $Name -ErrorAction Stop | Out-Null
Return $true
}
Catch { Return $false }
}
2024-06-17 16:48:50 -04:00
## Function to format a quantity of bytes into a denomination
#Function Format-ByteQuantity {
# param(
# [int64]$TotalBytes,
# [int]$Precision=2,
# [string]$Units,
# [switch]$WithUnitLabel
# )
# Switch ( $Units.ToUpper() ) {
# "KB" { $UnitLabel = "KB" ; $Divisor = 1KB }
# "MB" { $UnitLabel = "MB" ; $Divisor = 1MB }
# "GB" { $UnitLabel = "GB" ; $Divisor = 1GB }
# "TB" { $UnitLabel = "TB" ; $Divisor = 1TB }
# default { $UnitLabel = "bytes" ; $Divisor = 1 }
# }
# Switch ( $WithUnitLabel ) {
# $true { Return (ZeroPad-Decimal -Amount $([Math]::Round($TotalBytes / $Divisor,$Precision)) -NeededDecimalPlaces $Precision) + "${UnitLabel}" }
# $false { Return (ZeroPad-Decimal -Amount $([Math]::Round($TotalBytes / $Divisor,$Precision)) -NeededDecimalPlaces $Precision) }
# }
#}
#
## Function to pad zeros onto the end of a decimal number
#Function ZeroPad-Decimal {
# param(
# $Amount,
# $NeededDecimalPlaces
# )
# $splitNumbers = $Amount.ToString().Split('.')
# $paddedDecimal = $splitNumbers[1].PadRight($NeededDecimalPlaces, '0')
# Return $splitNumbers[0] + '.' + $paddedDecimal
#}
2022-12-15 12:03:47 -05:00
2023-01-04 10:42:37 -05:00
# Function to follow a URL and return the absolute URI of the result (whether redirected or not)
Function Get-AbsoluteURI {
param ([ string ] $URL )
2023-10-12 22:00:22 -04:00
If ( !( IsURLValid $URL ) ) { Return }
2023-01-04 10:42:37 -05:00
Return [ System.Net.HttpWebRequest ]:: Create ( $URL ). GetResponse (). ResponseUri . AbsoluteUri
}
2023-08-22 16:09:13 -04:00
Function Get-GUIDFromMSIUninstallString ([ string ] $str ) {
$start = $str . IndexOf ( '{' ) + 1
$end = $str . IndexOf ( '}' )
2023-08-23 15:01:36 -04:00
$retval = $str . Substring ( $start , $end - $start )
If ( $retval . Length -ne 36 ) { $retval = $null }
Return $retval
2023-08-22 16:09:13 -04:00
}
2023-11-01 21:04:52 -04:00
Function Get-GUIDFromMSIUninstallString ([ string ] $str ) {
$start = $str . IndexOf ( '{' ) + 1
$end = $str . IndexOf ( '}' )
$retval = $str . Substring ( $start , $end - $start )
If ( $retval . Length -ne 36 ) { $retval = $null }
Return $retval
}
2023-11-01 21:09:14 -04:00
2024-01-26 16:36:08 -05:00
# Function to uninstall an MSI package based on it's name
2023-11-06 14:36:41 -05:00
Function Uninstall-MSI {
2023-11-01 21:04:52 -04:00
param (
[ Parameter ( ValueFromPipeline = $true , Mandatory = $true )][ string[] ] $APP_NAMES ,
[ Parameter ( ValueFromPipeline = $false , Mandatory = $false )][ switch ] $Match ,
[ Parameter ( ValueFromPipeline = $false , Mandatory = $false )][ switch ] $IncludeNonMSI
)
BEGIN {
# Get all of the uninstall registry keys
$UNINSTALL_KEYS = Get-ChildItem -Path HKLM : \ SOFTWARE \ Microsoft \ Windows \ CurrentVersion \ Uninstall -ErrorAction SilentlyContinue
$UNINSTALL_KEYS += Get-ChildItem -Path HKLM : \ SOFTWARE \ WOW6432Node \ Microsoft \ Windows \ CurrentVersion \ Uninstall -ErrorAction SilentlyContinue
# Exit if we can't find any software at all
If ( $UNINSTALL_KEYS . Length -eq 0 ) { Write-Error "Could not find any installed apps in the Windows Registry" ; Return }
2023-11-01 21:14:33 -04:00
# Array to store custom objects about each installed MSI app
2023-11-01 21:04:52 -04:00
$INSTALLED_APPS = @ ()
Foreach ( $reg in $UNINSTALL_KEYS ) {
# Get the uninstall key
$REG_PROPERTY = Get-ItemProperty -Path $reg . PSPath
# Skip apps missing both the UninstallString and ModifyPath
If ( [ string ]:: IsNullOrEmpty ( $REG_PROPERTY . UninstallString ) -and [ string ]:: IsNullOrEmpty ( $REG_PROPERTY . ModifyPath ) ) { Continue }
# Determine if the app is installed via MSI
If ( $REG_PROPERTY . WindowsInstaller -eq 1 ) {
$MSI = $true
2023-11-01 21:14:33 -04:00
# Get the product code from the UninstallString
2023-11-01 21:04:52 -04:00
If ( ![ string ]:: IsNullOrEmpty ( $REG_PROPERTY . UninstallString ) ) { $PRODUCT_CODE = $ ( Get-GUIDFromMSIUninstallString -str $REG_PROPERTY . UninstallString ) }
# Get the product code from the ModifyPath
ElseIf ( ![ string ]:: IsNullOrEmpty ( $REG_PROPERTY . ModifyPath ) ) { $PRODUCT_CODE = $ ( Get-GUIDFromMSIUninstallString -str $REG_PROPERTY . ModifyPath ) }
# Don't add an app with an unknown product code
If ( [ string ]:: IsNullOrEmpty ( $PRODUCT_CODE ) ) { Continue }
} Else { $MSI = $false }
# Get a usable name for the entry
If ( ![ string ]:: IsNullOrEmpty ( $REG_PROPERTY . DisplayName ) ) {
$AppName = $REG_PROPERTY . DisplayName
} ElseIf ( ![ string ]:: IsNullOrEmpty ( $REG_PROPERTY . Name ) ) {
$AppName = $REG_PROPERTY . DisplayName
} Else {
$AppName = '[unknown]'
}
$customObject = [ PSCustomObject ] @ {
MSI = $MSI
DisplayName = $AppName
UninstallString = $REG_PROPERTY . UninstallString
ModifyPath = $REG_PROPERTY . ModifyPath
ProductCode = $PRODUCT_CODE
}
# TODO: Cleanup object properties
$INSTALLED_APPS += $customObject
}
}
PROCESS {
Foreach ( $app in $APP_NAMES ) {
Foreach ( $installed_app in $INSTALLED_APPS ) {
$uninstall = $false
# Do not add apps whose name does not match the one we're looking for
Switch ( $Match ) {
$true { If ( $installed_app . DisplayName -match $app ) { $uninstall = $true } }
$false { If ( $installed_app . DisplayName -ilike $app ) { $uninstall = $true } }
}
# Print out uninstall string for unsupported apps
2023-11-01 21:14:33 -04:00
If ( ( $uninstall ) -and ( $installed_app . MSI -eq $false ) ) {
2024-01-04 09:26:05 -05:00
# Skip apps that have no display name or uninstall string
If ( ([ string ]:: IsNullOrEmpty ( $DisplayName )) -and ([ string ]:: IsNullOrEmpty ( $UninstallString )) ) { $uninstall = $false ; Continue }
2023-11-01 21:04:52 -04:00
Write-Output "Cannot uninstall "" ${DisplayName} "" `n UninstallString: ${UninstallString} "
$uninstall = $false
}
If ( $uninstall ) {
$DisplayName = $installed_app . DisplayName
$UninstallString = $installed_app . UninstallString
$ProductCode = $installed_app . ProductCode
# Uninstall the app
Write-Output "Uninstalling "" ${DisplayName} "" with product code: ${ProductCode} "
$arguments = '/X{' + $ProductCode + '} /qn /norestart'
Try { Start-Process -FilePath "msiexec.exe" -ArgumentList $arguments -Wait }
Catch { Write-Error $_ . Exception . Message }
}
}
}
}
}
2023-08-22 16:09:13 -04:00
2023-09-07 10:59:55 -04:00
# Reboot an endpoint based on the amount of time it has been running without a reboot
Function Restart-EndpointOnUptime ([ timespan ] $MaxUptime ) {
# Get the length of time the endpoint has been running without restart
Try { $LastBootTime = ( Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop ). LastBootUpTime }
2023-10-05 13:11:38 -04:00
Catch { Write-Error $_ . Exception . Message ; Exit }
2023-09-07 10:59:55 -04:00
# Get the difference between the endpoint uptime and the current time
$Delta = New-TimeSpan -Start $LastBootTime -End $ ( Get-Date )
# Stop the function if the endpoint has not exceeded $MaxUptime
If ( $Delta -lt $MaxUptime ) { Exit }
# Reboot the endpoint
Try { Restart-Computer -Force -ErrorAction Stop }
2023-10-05 13:11:38 -04:00
Catch { Write-Error $_ . Exception . Message }
2023-09-07 10:59:55 -04:00
}
2024-06-25 16:06:37 -04:00
# Function to download and install local tools used by scripts
2024-06-17 16:45:21 -04:00
Function Install-LocalTools () {
param (
2024-06-25 16:06:37 -04:00
[ Parameter ( ValueFromPipeline = $false , Mandatory = $false )][ switch ] $Force = $false
2024-06-17 16:45:21 -04:00
)
$ToolURLs = @ (
'https://www.emberkom.com/tools/BlueScreenView-x64.exe'
'https://www.emberkom.com/tools/BlueScreenView-x86.exe'
'https://www.emberkom.com/tools/dnshelper.exe'
'https://www.emberkom.com/tools/etl2pcapng.exe'
)
Foreach ( $ToolURL in $ToolURLs ) {
$ToolName = Split-Path -Leaf $ToolURL
$ToolPath = $Global:ToolsDirectory + '\' + $ToolName
If ( ( Test-Path $ToolPath ) -and ( $Force -eq $false ) ) { Continue }
Write-Output "Installing ${ToolName} "
Download-File -URL $ToolURL -File $ToolPath
}
}
2024-06-25 16:06:37 -04:00
Function Source-PSScript ([ string ] $ScriptName ) {
If ( $ScriptName . StartsWith ( 'http://' ) -or $ScriptName . StartsWith ( 'https://' ) ) {
$URL = $ScriptName
} Else {
$URL = "https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/ ${ScriptName} .ps1"
}
$ScriptFile = Download-File -URL $URL
Write-Output "Sourcing ${ScriptFile} "
. $ScriptFile
If ( Test-Path $ScriptFile ) { Write-Output "Removing ${ScriptFile} " ; Remove-Item $ScriptFile -Force -ErrorAction SilentlyContinue }
2026-06-26 10:35:40 -04:00
}
# Forcefully maps a network drive but requires user's credentials to already exist in Credential Manager
Function Create-NetworkDrive {
param (
[ Parameter ( Mandatory )]
[ ValidatePattern ( "^[A-Z]:$" )]
[ string ] $DriveLetter ,
[ Parameter ( Mandatory )]
[ string ] $Path ,
[ Parameter ( Mandatory )]
[ string ] $Label
)
$existingDrive = Get-CimInstance Win32_LogicalDisk | Where-Object { $_ . DeviceID -eq $DriveLetter }
if ( -not $existingDrive ) {
Write-Output "Mapping ${Path} to drive letter ${DriveLetter} "
cmd . exe / c "net use $DriveLetter $Path /persistent:yes" | Out-Null
}
elseif ( $existingDrive . ProviderName -ne $Path ) {
Write-Output "Drive exists but points to a different path. Remapping..."
cmd . exe / c "net use $DriveLetter /delete /y" | Out-Null
cmd . exe / c "net use $DriveLetter $Path /persistent:yes" | Out-Null
}
else { Write-Verbose "Drive already mapped correctly." }
Start-Sleep -Seconds 2
try {
$dl = $DriveLetter . TrimEnd ( ':' )
$vol = Get-Volume -DriveLetter $dl -ErrorAction Stop
if ( $vol . FileSystemLabel -ne $Label ) {
Write-Output "Setting ${DriveLetter} drive label to "" ${Label} """
Set-Volume -DriveLetter $dl -NewFileSystemLabel $Label
}
}
catch { Write-Verbose "Could not set volume label (possibly unsupported context)." }
2024-06-25 16:06:37 -04:00
}
# Function to import the Syncro Module
2024-06-24 16:51:19 -04:00
Function Import-RMMModule () {
Try { Import-Module $env:SyncroModule -WarningAction SilentlyContinue -ErrorAction Stop }
Catch { Write-Error $_ . Exception . Message ; Return }
}
2024-06-25 16:06:37 -04:00
# Function to create a new ticket
2024-06-24 16:51:19 -04:00
Function New-RMMTicket () {
param (
[ Parameter ( ValueFromPipeline = $false , Mandatory = $true )][ string ] $Subject ,
[ Parameter ( ValueFromPipeline = $false , Mandatory = $false )][ string ] $IssueType = "Remote Support" ,
[ Parameter ( ValueFromPipeline = $false , Mandatory = $true )][ string ] $InitialIssue
)
2024-06-25 16:06:37 -04:00
If ( !( Get-Command Create-Syncro -Ticket -ErrorAction SilentlyContinue ) ) { Import-RMMModule }
2024-06-24 16:51:19 -04:00
Try { $Ticket = Create-Syncro -Ticket -Subject $Subject -IssueType $IssueType -Status "New" }
Catch { Write-Error $_ . Exception . Message ; Return }
$Global:TicketNumber = $Ticket . ticket . id
New-TicketComment -TicketID $Global:TicketNumber -Subject "Issue" -Comment $InitialIssue -Hidden -DoNotEmail
}
2024-06-25 16:06:37 -04:00
# Function to create a new comment on a ticket
2024-06-24 16:51:19 -04:00
Function New-TicketComment () {
param (
[ Parameter ( ValueFromPipeline = $false , Mandatory = $false )] $TicketID = $null ,
[ Parameter ( ValueFromPipeline = $false , Mandatory = $false )][ string ] $Subject = "Update" ,
[ Parameter ( ValueFromPipeline = $true , Mandatory = $true )][ string ] $Comment ,
[ Parameter ( ValueFromPipeline = $false , Mandatory = $false )][ switch ] $Hidden = $false ,
[ Parameter ( ValueFromPipeline = $false , Mandatory = $false )][ switch ] $DoNotEmail = $false
)
2024-06-25 16:06:37 -04:00
If ( !( Get-Command Create-Syncro -Ticket-Comment -ErrorAction SilentlyContinue ) ) { Import-RMMModule }
2024-06-24 16:51:19 -04:00
If ( ( $null -eq $TicketID ) -and ( $null -eq $Global:TicketNumber ) ) { Return }
If ( ( $null -eq $TicketID ) -and ( $null -ne $Global:TicketNumber ) ) { $TicketID = $Global:TicketNumber }
Try { Create-Syncro -Ticket-Comment -TicketIdOrNumber $TicketID -Subject $Subject -Body $Comment -Hidden $Hidden -DoNotEmail $DoNotEmail }
Catch { Write-Error $_ . Exception . Message ; Return }
}
2024-06-25 16:06:37 -04:00
# Function to create a new billable time entry on a ticket
Function New-TicketTimerEntry () {
param (
[ Parameter ( ValueFromPipeline = $false , Mandatory = $false )] $TicketID = $null ,
[ Parameter ( ValueFromPipeline = $false , Mandatory = $false )][ int ] $BillableMinutes = 15 ,
[ Parameter ( ValueFromPipeline = $true , Mandatory = $true )][ string ] $Comment ,
[ Parameter ( ValueFromPipeline = $false , Mandatory = $false )][ string ] $BillableUser = $Global:TimeAttributedToUser ,
[ Parameter ( ValueFromPipeline = $false , Mandatory = $false )][ switch ] $ChargeLater = $false
)
If ( $BillableUser -eq "" ) { Write-Error "Billable user not specified" ; Return }
If ( !( Get-Command Create-Syncro -Ticket-TimerEntry -ErrorAction SilentlyContinue ) ) { Import-RMMModule }
If ( $ChargeLater -eq $true ) { $ChargeTime = "false" } Else { $ChargeTime = "true" }
$StartAt = ( Get-Date ). AddMinutes (- $BillableMinutes ). ToString ( "o" )
Create-Syncro -Ticket-TimerEntry -TicketIdOrNumber $TicketID -StartTime $StartAt -DurationMinutes $BillableMinutes -Notes $Comment -UserIdOrEmail $BillableUser -ChargeTime $ChargeTime
}
2024-07-15 13:30:29 -04:00
# Function to get the SHA256 hash of a string
Function Get-StringHash () {
param ([ Parameter ( Mandatory = $true , ValueFromPipeline = $true )][ string ] $String )
$stream = [ IO.MemoryStream ]:: new ([ byte[]][char[] ] $String )
$hashObj = Get-FileHash -InputStream $stream -Algorithm SHA256
$hashObj . Hash
}
2024-08-23 11:18:44 -04:00
# Function to enable or disable the profile prompt when launching Outlook
Function Set-OutlookProfilePrompt () {
param (
[ Parameter ( ValueFromPipeline = $false , Mandatory = $true , ParameterSetName = 'enable' )][ switch ] $Enable ,
[ Parameter ( ValueFromPipeline = $false , Mandatory = $true , ParameterSetName = 'disable' )][ switch ] $Disable
)
If ( $Enable ) { $val = '1' ; $action = "Enabling" }
If ( $Disable ) { $val = '0' ; $action = "Disabling" }
Write-Output " ${action} profile picker on Outlook launch"
[ Microsoft.Win32.Registry ]:: SetValue ( 'HKEY_CURRENT_USER\SOFTWARE\Microsoft\Exchange\Client\Options' , 'PickLogonProfile' , $val ,[ Microsoft.Win32.RegistryValueKind ]:: String )
$Profiles = ( Get-ChildItem -Path 'HKCU:\SOFTWARE\Microsoft\Office\16.0\Outlook\Profiles' | Select-Object -ExpandProperty Name | Split-Path -Leaf | Sort-Object ) -join " `n "
Write-Output "Available Outlook profiles: `n ${Profiles} "
}
2024-11-06 10:07:39 -05:00
# Function to install and run script using Powershell 7
# Provided by j.mcbride from https://community.syncromsp.com/t/powershell-7-2/7425/5
Function Start-ScriptInPowershell7 () {
# Check for required PowerShell version (7+)
If (!( $PSVersionTable . PSVersion . Major -ge 7 )) {
Try {
# Install PowerShell 7 if missing
If (!( Test-Path " $env:SystemDrive \Program Files\PowerShell\7" )) {
Write-Output 'Installing PowerShell version 7...'
Invoke-Expression "& { $( Invoke-RestMethod https : // aka . ms / install-powershell . ps1 ) } -UseMSI -Quiet"
}
# Refresh PATH
$Env:Path = [ System.Environment ]:: GetEnvironmentVariable ( 'Path' , 'Machine' ) + ';' + [ System.Environment ]:: GetEnvironmentVariable ( 'Path' , 'User' )
# Restart script in PowerShell 7
pwsh -File " `" $PSCommandPath `" " @PSBoundParameters
}
Catch { Write-Error $_ . Exception . Message }
Finally { Exit $LASTEXITCODE }
}
# Input the actual script below this line
}
# Upgrade default TLS version to 1.2
Try { [ Net.ServicePointManager ]:: SecurityProtocol = [ Net.SecurityProtocolType ]:: Tls12 }
Catch { Write-Error $_ . Exception . Message }
2024-06-25 16:06:37 -04:00
# Initial setup of the MSP management directories and log file
2021-08-05 09:42:15 -04:00
Setup-MSPDirectories
2023-09-15 16:46:10 -04:00
Setup-LogFile
2024-06-25 16:06:37 -04:00
# Install the local tools
2025-03-22 08:19:55 -04:00
#Install-LocalTools
2024-06-25 16:06:37 -04:00
# Check the RMM runtime variable to see if a billable user was specified, if so then set the $TimeAttributedToUser
2024-08-06 15:00:13 -04:00
If ( $TimeAttributedToUser ) {
If ( $TimeAttributedToUser . Trim (). Length -ge 6 ) { $Global:TimeAttributedToUser = $TimeAttributedToUser . Trim (). ToLower () }
}
2024-06-25 16:06:37 -04:00
# Check the RMM runtime variable to see if a ticket number was specified, if so then set the $TicketID
2024-08-06 15:00:13 -04:00
If ( $TicketNumber ) {
If ( $TicketNumber . Trim (). Length -gt 1 ) { $Global:TicketNumber = $TicketNumber . Trim () }
}