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
}
2026-10-01 11:16:23 -04:00
# Convert an RMM text variable into an actual Boolean value.
Function ConvertTo-RmmBoolean {
<#
. SYNOPSIS
Converts an RMM text value to a System.Boolean.
. DESCRIPTION
Accepts true, $true, yes, y, or 1; false, $false, no, n, or 0.
Matching ignores case and surrounding whitespace. Null and empty values
return false. Unrecognized values throw instead of silently enabling an option.
.PARAMETER Value
The value to convert. Native Boolean values are also accepted.
.PARAMETER VariableName
Optional RMM variable name, without the leading $, for error messages.
. EXAMPLE
$ZipEnabled = ConvertTo-RmmBoolean -Value $Zip -VariableName 'Zip'
. EXAMPLE
ConvertTo-RmmBoolean ' No '
#>
[ CmdletBinding ()]
[ OutputType ([ bool ])]
param (
[ Parameter ( Mandatory = $true , Position = 0 , ValueFromPipeline = $true )]
[ AllowNull ()]
[ AllowEmptyString ()]
[ string ] $Value ,
[ string ] $VariableName = 'Value'
)
process {
$NormalizedValue = ([ string ] $Value ). Trim (). ToLowerInvariant ()
If ( $NormalizedValue -in @ ( 'true' , '$true' , 'yes' , 'y' , '1' ) ) {
Return $true
}
If ( $NormalizedValue -in @ ( '' , 'false' , '$false' , 'no' , 'n' , '0' ) ) {
Return $false
}
Throw ( 'Invalid ${0} value. Use true, $true, yes, y, 1, false, $false, no, n, or 0.' -f $VariableName )
}
}
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 ) }
2026-09-30 09:46:37 -04:00
# BITS errors must stop the caller instead of returning a failed download's path.
Try { Start-BitsTransfer -Source $URI -Destination $File -ErrorAction Stop }
Catch { Throw "BITS download failed from ${URI} to ${File} : $( $_ . Exception . Message ) " }
2023-11-01 15:36:58 -04:00
Return $File
2020-10-22 14:01:35 -04:00
}
2026-09-30 11:42:05 -04:00
# Downloads from the original URL; BITS handles redirects without preliminary web requests.
# Without -File, the temporary filename comes from the original URL, not its redirect target.
Function Download-FileDirectly {
param (
[ Parameter ( Mandatory = $true , ValueFromPipeline = $true )][ string ] $URL ,
[ Parameter ( Mandatory = $false , ValueFromPipeline = $false )][ string ] $File
)
If ( [ string ]:: IsNullOrEmpty ( $File ) ) { $File = '{0}{1}' -f ( Get-TempPath ), ( Split-Path $URL -Leaf ) }
Try { Start-BitsTransfer -Source $URL -Destination $File -ErrorAction Stop }
Catch { Throw "BITS download failed from ${URL} to ${File} : $( $_ . Exception . Message ) " }
Return $File
}
2026-09-30 07:45:01 -04:00
# Upload a file to a LiquidFiles Filedrop and submit its notification message.
Function Upload-File {
<#
. SYNOPSIS
Uploads a file to the Emberkom LiquidFiles Filedrop.
. DESCRIPTION
Uses the LiquidFiles 3.7+ JSON API with bounded binary chunks, including in
32-bit Windows PowerShell 5. The local file is retained. Returns a receipt
only after the Filedrop accepts the final message for delivery.
Uploads run synchronously. Allow enough time in the RMM for the entire
transfer; the temporary Filedrop API key expires after 24 hours. Failed
chunks are retried within this call, but restarting the script starts a
new transfer. Final message submission is not retried automatically,
because a lost response could otherwise cause duplicate notifications.
.PARAMETER Path
Literal path of one completed file. Also accepts -File or a pipeline path.
.PARAMETER From
Optional sender override. Defaults to the computer's fully qualified host
2026-10-01 09:36:23 -04:00
name at emberkom.com (or its host name when no DNS suffix is configured).
2026-09-30 07:45:01 -04:00
.PARAMETER ChunkSizeMB
Maximum upload buffer size in MiB; defaults to 10 regardless of file size.
.PARAMETER TimeoutSeconds
Timeout for each HTTP request, not for the entire transfer.
. EXAMPLE
Upload-File -Path $DsaResultPath
. EXAMPLE
Upload-File -File 'C:\Reports\report.zip' -Subject 'Diagnostic report' -Verbose
. LINK
https://docs.liquidfiles.com/api/v4.3/filedrop/
. LINK
https://docs.liquidfiles.com/api/v4.3/attachments/chunks.html
#>
[ CmdletBinding ()]
param (
[ Parameter ( Mandatory = $true , ValueFromPipeline = $true , ValueFromPipelineByPropertyName = $true )]
[ Alias ( 'File' , 'FullName' )][ ValidateNotNullOrEmpty ()][ string ] $Path ,
[ ValidateNotNullOrEmpty ()][ string ] $FileDropUrl = 'https://xfer.emberkom.com/filedrop/cmd' ,
[ string ] $From ,
[ string ] $Subject ,
[ string ] $Message ,
[ ValidateRange ( 1 , 100 )][ int ] $ChunkSizeMB = 10 ,
[ ValidateRange ( 1 , 3600 )][ int ] $TimeoutSeconds = 900 ,
[ ValidateRange ( 0 , 10 )][ int ] $RetryCount = 3
)
PROCESS {
$UploadUri = [ uri ] $FileDropUrl
If ( ! $UploadUri . IsAbsoluteUri -or $UploadUri . Scheme -ne 'https' -or
$UploadUri . UserInfo -or $UploadUri . Query -or $UploadUri . Fragment ) {
Throw 'FileDropUrl must be an absolute HTTPS Filedrop URL without credentials, query, or fragment.'
}
$UploadBaseUrl = $UploadUri . AbsoluteUri . TrimEnd ( '/' )
$UploadFile = Get-Item -LiteralPath $Path -Force -ErrorAction Stop
If ( $UploadFile -isnot [ System.IO.FileInfo ] ) { Throw 'Upload-File requires a file, not a directory.' }
$UploadHostInfo = [ System.Net.NetworkInformation.IPGlobalProperties ]:: GetIPGlobalProperties ()
$UploadHostName = $UploadHostInfo . HostName
If ( [ string ]:: IsNullOrWhiteSpace ( $UploadHostName ) ) { $UploadHostName = [ Environment ]:: MachineName }
$UploadDnsSuffix = $UploadHostInfo . DomainName . Trim ( '.' )
If ( $UploadDnsSuffix -and ! $UploadHostName . EndsWith ( ". ${UploadDnsSuffix} " , [ StringComparison ]:: OrdinalIgnoreCase ) ) {
$UploadHostName = " ${UploadHostName} . ${UploadDnsSuffix} "
}
$UploadSender = $From
2026-10-01 09:36:23 -04:00
If ( [ string ]:: IsNullOrWhiteSpace ( $UploadSender ) ) { $UploadSender = $UploadHostName . ToLowerInvariant () + '@emberkom.com' }
2026-09-30 07:45:01 -04:00
Try { $UploadSender = ([ System.Net.Mail.MailAddress ]:: new ( $UploadSender )). Address }
Catch { Throw 'From must be a valid sender email address.' }
$UploadSubject = $Subject
If ( [ string ]:: IsNullOrWhiteSpace ( $UploadSubject ) ) { $UploadSubject = "File upload from ${UploadHostName} : $( $UploadFile . Name ) " }
$UploadMessage = $Message
If ( [ string ]:: IsNullOrWhiteSpace ( $UploadMessage ) ) { $UploadMessage = "Uploaded by ${UploadHostName} ." }
Add-Type -AssemblyName System . Net . Http -ErrorAction Stop
$UploadClient = $null
$UploadHandler = $null
$UploadStream = $null
$UploadApiKey = $null
# Keep the same HTTP client (and cookies) for every chunk in this session.
Function Invoke-FileDropRequest {
param (
[ string ] $Method ,
[ string ] $Uri ,
[ byte[] ] $Data ,
[ int ] $Count = 0 ,
[ string ] $ContentType ,
[ int ] $Retries = $RetryCount
)
For ( $UploadAttempt = 0 ; $UploadAttempt -le $Retries ; $UploadAttempt ++ ) {
$UploadRequest = $null
$UploadResponse = $null
$UploadStatus = 0
Try {
$UploadRequest = [ System.Net.Http.HttpRequestMessage ]:: new ([ System.Net.Http.HttpMethod ]:: new ( $Method ), $Uri )
If ( $Method -eq 'POST' ) {
$UploadRequest . Content = [ System.Net.Http.ByteArrayContent ]:: new ( $Data , 0 , $Count )
If ( $ContentType ) {
$UploadRequest . Content . Headers . ContentType = [ System.Net.Http.Headers.MediaTypeHeaderValue ]:: Parse ( $ContentType )
}
}
$UploadResponse = $UploadClient . SendAsync ( $UploadRequest ). GetAwaiter (). GetResult ()
$UploadStatus = [ int ] $UploadResponse . StatusCode
$UploadResponseText = $UploadResponse . Content . ReadAsStringAsync (). GetAwaiter (). GetResult ()
If ( ! $UploadResponse . IsSuccessStatusCode ) {
# Do not include authentication tokens in errors or RMM logs.
If ( $UploadApiKey ) { $UploadResponseText = $UploadResponseText . Replace ( $UploadApiKey , '[redacted]' ) }
If ( $UploadResponseText . Length -gt 1000 ) { $UploadResponseText = $UploadResponseText . Substring ( 0 , 1000 ) }
Throw "Filedrop returned HTTP ${UploadStatus} : ${UploadResponseText} "
}
If ( [ string ]:: IsNullOrWhiteSpace ( $UploadResponseText ) ) { Return $null }
$UploadResponseData = ConvertFrom-Json -InputObject $UploadResponseText -ErrorAction Stop
If ( $UploadResponseData . errors ) { Throw ( 'Filedrop rejected the request: ' + ( $UploadResponseData . errors -join '; ' )) }
Return $UploadResponseData
}
Catch {
$UploadCanRetry = $UploadStatus -eq 0 -or $UploadStatus -in @ ( 408 , 429 , 500 , 502 , 503 , 504 )
If ( ! $UploadCanRetry -or $UploadAttempt -ge $Retries ) { Throw }
Write-Verbose "Retrying Filedrop request after a connection error or HTTP ${UploadStatus} ."
}
Finally {
If ( $null -ne $UploadResponse ) { $UploadResponse . Dispose () }
If ( $null -ne $UploadRequest ) { $UploadRequest . Dispose () }
}
Start-Sleep -Seconds ([ Math ]:: Min ( 30 , [ Math ]:: Pow ( 2 , $UploadAttempt + 1 )))
}
}
Try {
# Deny writes while reading so retried chunks always contain the same bytes.
$UploadStream = [ System.IO.File ]:: Open ( $UploadFile . FullName , [ System.IO.FileMode ]:: Open ,
[ System.IO.FileAccess ]:: Read , [ System.IO.FileShare ]:: Read )
[ long ] $UploadLength = $UploadStream . Length
$UploadHandler = [ System.Net.Http.HttpClientHandler ]:: new ()
$UploadHandler . AllowAutoRedirect = $false
$UploadClient = New-Object -TypeName System . Net . Http . HttpClient -ArgumentList $UploadHandler
$UploadClient . Timeout = [ TimeSpan ]:: FromSeconds ( $TimeoutSeconds )
$UploadClient . DefaultRequestHeaders . Accept . ParseAdd ( 'application/json' )
$UploadInfo = ( Invoke-FileDropRequest -Method GET -Uri $UploadBaseUrl ). filedrop
$UploadApiKey = [ string ] $UploadInfo . api_key
If ( [ string ]:: IsNullOrWhiteSpace ( $UploadApiKey ) ) {
Throw 'The Filedrop did not provide an API key. Check its URL, password, and email-validation requirements.'
}
If ( $UploadInfo . max_upload_size -gt 0 -and $UploadLength -gt ([ long ] $UploadInfo . max_upload_size * 1 MB) ) {
Throw "The file exceeds the Filedrop limit of $( $UploadInfo . max_upload_size ) MiB."
}
$UploadExtension = $UploadFile . Extension . TrimStart ( '.' ). ToLowerInvariant ()
$UploadPermitted = $UploadInfo . permitted_extensions
If ( ! $UploadPermitted ) { $UploadPermitted = $UploadInfo . limited_extensions }
$UploadAllowedExtensions = @ (([ string ] $UploadPermitted -split '[,\s]+' ) | ForEach-Object { $_ . TrimStart ( '.' ) } | Where-Object { $_ })
$UploadBlockedExtensions = @ (([ string ] $UploadInfo . blocked_extensions -split '[,\s]+' ) | ForEach-Object { $_ . TrimStart ( '.' ) } | Where-Object { $_ })
If ( ( $UploadAllowedExtensions . Count -gt 0 -and $UploadExtension -notin $UploadAllowedExtensions ) -or
$UploadExtension -in $UploadBlockedExtensions ) {
Throw "The Filedrop does not permit the file extension ' $UploadExtension '."
}
$UploadAuth = [ Convert ]:: ToBase64String ([ Text.Encoding ]:: UTF8 . GetBytes ( $UploadApiKey + ':x' ))
$UploadClient . DefaultRequestHeaders . Authorization = [ System.Net.Http.Headers.AuthenticationHeaderValue ]:: new ( 'Basic' , $UploadAuth )
[ int ] $UploadChunkSize = $ChunkSizeMB * 1 MB
[ long ] $UploadChunks = [ Math ]:: Max ( 1 , [ Math ]:: Ceiling ( $UploadLength / [ double ] $UploadChunkSize ))
$UploadBuffer = [ byte[] ]:: new ([ int][Math ]:: Min ( $UploadChunkSize , [ Math ]:: Max ( 1 , $UploadLength )))
$UploadEncodedName = [ uri ]:: EscapeDataString ( $UploadFile . Name )
$UploadAttachment = $null
Write-Verbose "Uploading $( $UploadFile . Name ) ( $UploadLength bytes) in $UploadChunks chunk(s) as ${UploadSender} ."
For ( [ long ] $UploadChunk = 0 ; $UploadChunk -lt $UploadChunks ; $UploadChunk ++ ) {
[ int ] $UploadBytesNeeded = [ Math ]:: Min ( $UploadChunkSize , $UploadLength - $UploadStream . Position )
[ int ] $UploadBytesRead = 0
While ( $UploadBytesRead -lt $UploadBytesNeeded ) {
$UploadRead = $UploadStream . Read ( $UploadBuffer , $UploadBytesRead , $UploadBytesNeeded - $UploadBytesRead )
If ( $UploadRead -eq 0 ) { Throw 'The local file ended before all expected bytes were read.' }
$UploadBytesRead += $UploadRead
}
$UploadChunkUrl = " ${UploadBaseUrl} /attachments/upload?filename= ${UploadEncodedName} &chunk= ${UploadChunk} &chunks= ${UploadChunks} "
$UploadChunkResult = Invoke-FileDropRequest -Method POST -Uri $UploadChunkUrl -Data $UploadBuffer -Count $UploadBytesRead
If ( $UploadChunkResult . attachment . id ) { $UploadAttachment = $UploadChunkResult . attachment }
Write-Progress -Activity "Uploading $( $UploadFile . Name ) " -Status "Chunk $( $UploadChunk + 1 ) of ${UploadChunks} " `
-PercentComplete ([ int ]( 100 * ( $UploadChunk + 1 ) / $UploadChunks ))
}
If ( ! $UploadAttachment . id ) { Throw 'The upload did not return an attachment ID; the Filedrop message was not submitted.' }
If ( $null -ne $UploadAttachment . size -and [ long ] $UploadAttachment . size -ne $UploadLength ) {
Throw 'The uploaded attachment size does not match the local file; the Filedrop message was not submitted.'
}
If ( $UploadAttachment . content_blocked ) { Throw 'LiquidFiles blocked this attachment; the Filedrop message was not submitted.' }
$UploadBody = @ { message = @ {
from = $UploadSender
subject = $UploadSubject
message = $UploadMessage
attachments = @ ([ string ] $UploadAttachment . id )
} } | ConvertTo-Json -Depth 4 -Compress
$UploadBodyBytes = [ Text.Encoding ]:: UTF8 . GetBytes ( $UploadBody )
Try {
$UploadReceipt = Invoke-FileDropRequest -Method POST -Uri $UploadBaseUrl -Data $UploadBodyBytes `
-Count $UploadBodyBytes . Length -ContentType 'application/json; charset=utf-8' -Retries 0
If ( [ string ]:: IsNullOrWhiteSpace ([ string ] $UploadReceipt . message . status ) ) {
Throw 'The server did not return a Filedrop submission confirmation.'
}
}
Catch {
Throw "Filedrop submission was not confirmed. Check the Filedrop before retrying to avoid duplicate notifications. $( $_ . Exception . Message ) "
}
[ pscustomobject ] @ {
Path = $UploadFile . FullName
FileDropUrl = $UploadBaseUrl
From = $UploadSender
AttachmentId = [ string ] $UploadAttachment . id
Size = $UploadLength
Status = [ string ] $UploadReceipt . message . status
}
}
Finally {
Write-Progress -Activity "Uploading $( $UploadFile . Name ) " -Completed
If ( $null -ne $UploadStream ) { $UploadStream . Dispose () }
If ( $null -ne $UploadClient ) { $UploadClient . Dispose () }
ElseIf ( $null -ne $UploadHandler ) { $UploadHandler . Dispose () }
$UploadApiKey = $null
}
}
}
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 = @ (
2026-09-21 15:05:32 -04:00
'https://xfer.emberkom.com/shares/tools/folders/9b9988fb-c53a-4833-a4ad-68d01a3021bb/files/bluescreenview-x64-exe/download/BlueScreenView-x64.exe'
'https://xfer.emberkom.com/shares/tools/folders/9b9988fb-c53a-4833-a4ad-68d01a3021bb/files/bluescreenview-x86-exe/download/BlueScreenView-x86.exe'
'https://xfer.emberkom.com/shares/tools/folders/9b9988fb-c53a-4833-a4ad-68d01a3021bb/files/etl2pcapng-exe/download/etl2pcapng.exe'
2024-06-17 16:45:21 -04:00
)
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
}
2026-07-24 14:43:37 -04:00
function Install-SystemFont {
[ CmdletBinding ( SupportsShouldProcess = $true )]
param (
[ Parameter ( Mandatory = $true )]
[ ValidateNotNullOrEmpty ()]
[ string ] $FontPath
)
try {
$fontFile = Get-Item -LiteralPath $FontPath -ErrorAction Stop
}
catch {
Write-Error "Font file ' $FontPath ' could not be found or accessed: $( $_ . Exception . Message ) "
return
}
if ( $fontFile . PSIsContainer ) {
Write-Error "Font path ' $FontPath ' points to a directory. Specify a .otf or .ttf file."
return
}
$extension = $fontFile . Extension . ToLowerInvariant ()
if ( $extension -notin @ ( '.otf' , '.ttf' )) {
Write-Error "Only .otf and .ttf font files are supported."
return
}
# Read the font's embedded family and face names instead of relying on
# language- and Windows-version-dependent Shell property column numbers.
try {
Add-Type -AssemblyName PresentationCore -ErrorAction Stop
$glyphTypeface = New-Object System . Windows . Media . GlyphTypeface ([ Uri ] $fontFile . FullName )
$englishLanguage = [ System.Globalization.CultureInfo ]:: GetCultureInfo ( 'en-US' )
$fontFamily = $null
$fontFace = $null
if ( -not $glyphTypeface . FamilyNames . TryGetValue ( $englishLanguage , [ ref ] $fontFamily )) {
$fontFamily = $glyphTypeface . FamilyNames . Values | Select-Object -First 1
}
if ( -not $glyphTypeface . FaceNames . TryGetValue ( $englishLanguage , [ ref ] $fontFace )) {
$fontFace = $glyphTypeface . FaceNames . Values | Select-Object -First 1
}
if ([ string ]:: IsNullOrWhiteSpace ( $fontFamily )) {
throw "The font does not contain a readable family name."
}
if ([ string ]:: IsNullOrWhiteSpace ( $fontFace ) -or $fontFace -in @ ( 'Normal' , 'Regular' )) {
$fontName = $fontFamily
}
else {
$fontName = " $fontFamily $fontFace "
}
}
catch {
Write-Error "Font metadata could not be read from ' $( $fontFile . FullName ) ': $( $_ . Exception . Message ) "
return
}
$fileName = $fontFile . Name
$fontsDirectory = Join-Path $env:SystemRoot 'Fonts'
$destinationPath = Join-Path $fontsDirectory $fileName
$registryValueName = if ( $extension -eq '.otf' ) {
" $fontName (OpenType)"
}
else {
" $fontName (TrueType)"
}
try {
if ( -not ( 'Emberkom.NativeFontMethods' -as [ type ])) {
Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
namespace Emberkom
{
public static class NativeFontMethods
{
[DllImport("gdi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int AddFontResource(string fileName);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr SendMessageTimeout(
IntPtr windowHandle,
uint message,
UIntPtr wParam,
IntPtr lParam,
uint flags,
uint timeout,
out UIntPtr result
);
}
}
'@ -ErrorAction Stop
}
}
catch {
Write-Error "Windows font installation support could not be initialized: $( $_ . Exception . Message ) "
return
}
if ( -not $PSCmdlet . ShouldProcess ( $destinationPath , "Install system font ' $fontName '" )) {
return
}
try {
$copyRequired = $true
if ( Test-Path -LiteralPath $destinationPath ) {
$sourceHash = ( Get-FileHash -LiteralPath $fontFile . FullName -Algorithm SHA256 -ErrorAction Stop ). Hash
$destinationHash = ( Get-FileHash -LiteralPath $destinationPath -Algorithm SHA256 -ErrorAction Stop ). Hash
$copyRequired = $sourceHash -ne $destinationHash
if ( -not $copyRequired ) {
Write-Output "Font file ' $fileName ' is already present in ' $fontsDirectory '."
}
}
if ( $copyRequired ) {
Copy-Item -LiteralPath $fontFile . FullName -Destination $destinationPath -Force -ErrorAction Stop
Write-Output "Copied font file ' $fileName ' to ' $fontsDirectory '."
}
$fontRegistryKey = [ Microsoft.Win32.Registry ]:: LocalMachine . OpenSubKey (
'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts' ,
$true
)
if ( $null -eq $fontRegistryKey ) {
throw "The system Fonts registry key could not be opened for writing."
}
try {
$fontRegistryKey . SetValue (
$registryValueName ,
$fileName ,
[ Microsoft.Win32.RegistryValueKind ]:: String
)
}
finally {
$fontRegistryKey . Dispose ()
}
Write-Output "Registered ' $registryValueName ' as a system font."
$fontsAdded = [ Emberkom.NativeFontMethods ]:: AddFontResource ( $destinationPath )
if ( $fontsAdded -eq 0 ) {
throw "Windows did not load ' $destinationPath ' into the current session."
}
Write-Output "Loaded ' $fontName ' into the current Windows session."
# Notify applications in this session without allowing a hung window to
# block an unattended RMM script indefinitely.
$broadcastHandle = [ IntPtr ] 0xffff
$fontChangeMessage = 0x001D
$abortIfHung = 0x0002
$messageResult = [ UIntPtr ]:: Zero
$notificationSent = [ Emberkom.NativeFontMethods ]:: SendMessageTimeout (
$broadcastHandle ,
$fontChangeMessage ,
[ UIntPtr ]:: Zero ,
[ IntPtr ]:: Zero ,
$abortIfHung ,
1000 ,
[ ref ] $messageResult
)
if ( $notificationSent -eq [ IntPtr ]:: Zero ) {
Write-Output "Installed system font ' $fontName ', but no application acknowledged the font-change notification."
return
}
Write-Output "Installed system font ' $fontName ' successfully."
}
catch {
Write-Error "Failed to install font ' $( $fontFile . FullName ) ': $( $_ . Exception . Message ) "
}
}
# Usage Example:
# Install-SystemFont -FontPath "C:\Path\To\YourFont.otf"
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 () }
2026-07-24 14:43:37 -04:00
}