Compare commits
5
Commits
1e5739b75d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af1ccf5fa1 | ||
|
|
1b68d12ab3 | ||
|
|
f5e12804be | ||
|
|
912bff8d81 | ||
|
|
8a53de4430 |
@@ -0,0 +1,21 @@
|
|||||||
|
# Repository Guidance
|
||||||
|
|
||||||
|
## RMM script execution model
|
||||||
|
|
||||||
|
Scripts in this repository are generally executed by a small caller script in the RMM. The caller first downloads and dot-sources `Tools.ps1` from the public repository URL:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
```
|
||||||
|
|
||||||
|
The caller then invokes `Run-Script`, passing the repository script's filename without the `.ps1` extension:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Run-Script -LivePSScript Create-BatteryReport
|
||||||
|
```
|
||||||
|
|
||||||
|
For a live PowerShell script without `-Arguments`, `Run-Script` downloads the target script and dot-sources it. This keeps the core script logic in version control, and changes pushed to the repository take effect in production without updating each RMM caller.
|
||||||
|
|
||||||
|
When a repository script needs RMM-provided input, define the variable in the RMM caller before calling `Run-Script`. Because the target is dot-sourced into the caller's scope, it can read that variable directly.
|
||||||
|
|
||||||
|
When changing `Run-Script` or scripts invoked through it, preserve this shared-scope behavior unless the change explicitly includes migrating all affected RMM callers.
|
||||||
@@ -13,10 +13,67 @@ If ( Test-Path $HtmlReportPath ) { Remove-Item -Path $HtmlReportPath -Force }
|
|||||||
|
|
||||||
# Generate the XML report
|
# Generate the XML report
|
||||||
Write-Output "Generating XML battery report: ${XmlReportPath}"
|
Write-Output "Generating XML battery report: ${XmlReportPath}"
|
||||||
Try { Start-Process "powercfg.exe" -ArgumentList "/batteryreport /output ""${XmlReportPath}"" /xml" -Wait }
|
|
||||||
Catch { Write-Error $_.Exception.Message }
|
Try {
|
||||||
|
$XmlProcess = Start-Process "powercfg.exe" `
|
||||||
|
-ArgumentList "/batteryreport /output ""${XmlReportPath}"" /xml" `
|
||||||
|
-Wait `
|
||||||
|
-PassThru `
|
||||||
|
-ErrorAction Stop
|
||||||
|
}
|
||||||
|
Catch {
|
||||||
|
Write-Error $_.Exception.Message
|
||||||
|
Exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make sure powercfg succeeded and actually produced the report
|
||||||
|
If ( $XmlProcess.ExitCode -ne 0 ) {
|
||||||
|
Write-Error "powercfg failed to generate the XML battery report with exit code $($XmlProcess.ExitCode)"
|
||||||
|
Exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
If ( !(Test-Path -LiteralPath $XmlReportPath -PathType Leaf) ) {
|
||||||
|
Write-Error "The XML battery report was not created: ${XmlReportPath}"
|
||||||
|
Exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate the generated XML and confirm that it contains battery records
|
||||||
|
Try {
|
||||||
|
[xml]$GeneratedBatteryReport = Get-Content `
|
||||||
|
-LiteralPath $XmlReportPath `
|
||||||
|
-Raw `
|
||||||
|
-ErrorAction Stop
|
||||||
|
}
|
||||||
|
Catch {
|
||||||
|
Write-Error "The generated battery report is not valid XML: $($_.Exception.Message)"
|
||||||
|
Exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$GeneratedBatteries = @(
|
||||||
|
$GeneratedBatteryReport.BatteryReport.Batteries.Battery
|
||||||
|
)
|
||||||
|
|
||||||
|
If ( $GeneratedBatteries.Count -eq 0 ) {
|
||||||
|
Write-Error "The generated battery report contains no battery records"
|
||||||
|
Exit 1
|
||||||
|
}
|
||||||
|
|
||||||
# Convert XML report to HTML
|
# Convert XML report to HTML
|
||||||
Write-Output "Generating HTML battery report: ${HtmlReportPath}"
|
Write-Output "Generating HTML battery report: ${HtmlReportPath}"
|
||||||
Try { Start-Process "powercfg.exe" -ArgumentList "/batteryreport /transformxml ""${XmlReportPath}"" /output ""${HtmlReportPath}""" -Wait }
|
|
||||||
Catch { Write-Error $_.Exception.Message }
|
Try {
|
||||||
|
$HtmlProcess = Start-Process "powercfg.exe" `
|
||||||
|
-ArgumentList "/batteryreport /transformxml ""${XmlReportPath}"" /output ""${HtmlReportPath}""" `
|
||||||
|
-Wait `
|
||||||
|
-PassThru `
|
||||||
|
-ErrorAction Stop
|
||||||
|
}
|
||||||
|
Catch {
|
||||||
|
Write-Error $_.Exception.Message
|
||||||
|
Exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
If ( $HtmlProcess.ExitCode -ne 0 -or !(Test-Path -LiteralPath $HtmlReportPath -PathType Leaf) ) {
|
||||||
|
Write-Error "Failed to create the HTML battery report"
|
||||||
|
Exit 1
|
||||||
|
}
|
||||||
@@ -1126,6 +1126,185 @@ Function Create-NetworkDrive {
|
|||||||
catch { Write-Verbose "Could not set volume label (possibly unsupported context)." }
|
catch { Write-Verbose "Could not set volume label (possibly unsupported context)." }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
# Function to import the Syncro Module
|
# Function to import the Syncro Module
|
||||||
Function Import-RMMModule () {
|
Function Import-RMMModule () {
|
||||||
Try { Import-Module $env:SyncroModule -WarningAction SilentlyContinue -ErrorAction Stop }
|
Try { Import-Module $env:SyncroModule -WarningAction SilentlyContinue -ErrorAction Stop }
|
||||||
@@ -1244,4 +1423,4 @@ If ( $TimeAttributedToUser ) {
|
|||||||
# Check the RMM runtime variable to see if a ticket number was specified, if so then set the $TicketID
|
# Check the RMM runtime variable to see if a ticket number was specified, if so then set the $TicketID
|
||||||
If ( $TicketNumber ) {
|
If ( $TicketNumber ) {
|
||||||
If ( $TicketNumber.Trim().Length -gt 1 ) { $Global:TicketNumber = $TicketNumber.Trim() }
|
If ( $TicketNumber.Trim().Length -gt 1 ) { $Global:TicketNumber = $TicketNumber.Trim() }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user