Compare commits
275
Commits
9409f000e0
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
819b8d72f0 | ||
|
|
a53dcfb6a9 | ||
|
|
2efb7116d8 | ||
|
|
3b8ceeac5a | ||
|
|
4912bbc28e | ||
|
|
0975f5d24a | ||
|
|
4e06ede640 | ||
|
|
4e6c35a8f4 | ||
|
|
35c1904d0d | ||
|
|
c74fe82d36 | ||
|
|
745902f8c2 | ||
|
|
bb44f2a9ef | ||
|
|
a47be2f91b | ||
|
|
7a25ff2d6d | ||
|
|
164dc1ac58 | ||
|
|
1732f23d37 | ||
|
|
af1ccf5fa1 | ||
|
|
1b68d12ab3 | ||
|
|
f5e12804be | ||
|
|
912bff8d81 | ||
|
|
8a53de4430 | ||
|
|
1e5739b75d | ||
|
|
db8fc541df | ||
|
|
faddaba0ed | ||
|
|
8ae07c0030 | ||
|
|
4a7e0642ef | ||
|
|
3470464b1d | ||
|
|
cf40e50c30 | ||
|
|
ac85a06003 | ||
|
|
cce2950d10 | ||
|
|
41260f438b | ||
|
|
1e10306b61 | ||
|
|
631ce956fd | ||
|
|
06b66813cf | ||
|
|
5fa2d50bd6 | ||
|
|
26191c9e58 | ||
|
|
91c3bc858d | ||
|
|
9d36fabf00 | ||
|
|
d2824cb771 | ||
|
|
30fe5e32d6 | ||
|
|
e7fe443894 | ||
|
|
f446086e38 | ||
|
|
30c68220f2 | ||
|
|
a17848a5eb | ||
|
|
930c9eca73 | ||
|
|
cbee4335f3 | ||
|
|
751af98c6e | ||
|
|
7a74d6d2a9 | ||
|
|
79290125f4 | ||
|
|
ac870b4e82 | ||
|
|
a9ff804048 | ||
|
|
330501c551 | ||
|
|
b55d302311 | ||
|
|
3b4532529b | ||
|
|
253b714fe2 | ||
|
|
c6d587db8e | ||
|
|
5da50abc5a | ||
|
|
1c50712eb0 | ||
|
|
c6bd2e7c49 | ||
|
|
04d714cc5b | ||
|
|
31312fb510 | ||
|
|
f2b6a44169 | ||
|
|
1c582e7b0d | ||
|
|
baa6f76e1a | ||
|
|
a378cf2cc0 | ||
|
|
93f916e48a | ||
|
|
6abcc7a729 | ||
|
|
9781af8745 | ||
|
|
d6653c4554 | ||
|
|
d4acdaae9e | ||
|
|
1aab6b3755 | ||
|
|
227f940ca1 | ||
|
|
821505014d | ||
|
|
3f89290295 | ||
|
|
432133775a | ||
|
|
49b0f25369 | ||
|
|
c3a89f73bb | ||
|
|
1036cca39a | ||
|
|
92c75d8f22 | ||
|
|
7193b6e596 | ||
|
|
0fededf036 | ||
|
|
910cc65444 | ||
|
|
b3ebdd4434 | ||
|
|
61b0c676bc | ||
|
|
0cbc618d05 | ||
|
|
af7c4af9b6 | ||
|
|
65f8e2c6a1 | ||
|
|
8fca4894cd | ||
|
|
4a5043ca74 | ||
|
|
8ace7c6be1 | ||
|
|
288a1a4d9c | ||
|
|
fdc0e536cc | ||
|
|
39ae5a4235 | ||
|
|
ff6d6bb88c | ||
|
|
bbc6cdb142 | ||
|
|
01c06ad64b | ||
|
|
38d0e87a2e | ||
|
|
5bc1437398 | ||
|
|
cf575be0c6 | ||
|
|
20408316df | ||
|
|
b8758fec60 | ||
|
|
7a91bce87a | ||
|
|
6dc5a4efce | ||
|
|
ececf83389 | ||
|
|
cec26755f9 | ||
|
|
63792a557f | ||
|
|
87f7a75c9c | ||
|
|
5676dbb554 | ||
|
|
8e0e3711f7 | ||
|
|
efea29ac95 | ||
|
|
11317ef618 | ||
|
|
644cd0cd14 | ||
|
|
b0772bcb1c | ||
|
|
b8d9b7b9d3 | ||
|
|
98565b7fff | ||
|
|
67d291e2eb | ||
|
|
8d2913cd5a | ||
|
|
4b740b06e8 | ||
|
|
a447ac5652 | ||
|
|
298b8ef050 | ||
|
|
2885d975f4 | ||
|
|
659f03bf3a | ||
|
|
96c3e03b89 | ||
|
|
e872cdf02a | ||
|
|
6904647871 | ||
|
|
2dd6384c57 | ||
|
|
dbb6fefa07 | ||
|
|
bcf8b87dc5 | ||
|
|
689cfa66df | ||
|
|
0127474dbe | ||
|
|
bb682a43ee | ||
|
|
98c1b3e214 | ||
|
|
f77479c7b7 | ||
|
|
68114af27e | ||
|
|
f4905fec68 | ||
|
|
b82f741a79 | ||
|
|
676dd0011c | ||
|
|
6c27d5fa18 | ||
|
|
c753b13f1e | ||
|
|
b5bfe4b814 | ||
|
|
907ee882bf | ||
|
|
cbdca2b4eb | ||
|
|
7986d28dfc | ||
|
|
4e85dee127 | ||
|
|
b738b25b9a | ||
|
|
ebc98e979e | ||
|
|
ea6f7cd489 | ||
|
|
dff96bbd12 | ||
|
|
c078638e1d | ||
|
|
05da2d726e | ||
|
|
427e694fb3 | ||
|
|
475f517417 | ||
|
|
16e8701bfc | ||
|
|
2ae279c429 | ||
|
|
5a03788ca2 | ||
|
|
cc3f42b539 | ||
|
|
f82ce18319 | ||
|
|
4c32fe1c1e | ||
|
|
5b4c3f2f7a | ||
|
|
35b8e4bbbb | ||
|
|
f07edb4044 | ||
|
|
4c2e9979f1 | ||
|
|
4275e667df | ||
|
|
ad0cfb45f3 | ||
|
|
a425a38c25 | ||
|
|
3f45c70b3a | ||
|
|
04b7ba11c6 | ||
|
|
3cb28f273e | ||
|
|
f9f7045912 | ||
|
|
b694033ace | ||
|
|
f7fd81c048 | ||
|
|
6941d7aad4 | ||
|
|
4cec14c893 | ||
|
|
33960214fd | ||
|
|
ecc9ebcc72 | ||
|
|
27a12a2b02 | ||
|
|
3904441949 | ||
|
|
af90aba365 | ||
|
|
20cefe10b6 | ||
|
|
7c95440fd4 | ||
|
|
92b603c312 | ||
|
|
1388b9b35d | ||
|
|
55985e0a3e | ||
|
|
9cf6a0a7e8 | ||
|
|
c3b9bb9a64 | ||
|
|
10a1b2affb | ||
|
|
75d2f83d3a | ||
|
|
7bf14c30b5 | ||
|
|
c33adb91bd | ||
|
|
dab76d733c | ||
|
|
54510e3007 | ||
|
|
5167cdcd76 | ||
|
|
80877ded48 | ||
|
|
71d1b77457 | ||
|
|
c906da8ee3 | ||
|
|
3bb28f34c1 | ||
|
|
a3bd47580a | ||
|
|
5e0e080be5 | ||
|
|
4c5f608457 | ||
|
|
098c590084 | ||
|
|
09fdfa5109 | ||
|
|
175248ec4d | ||
|
|
497126d138 | ||
|
|
401cc3ded1 | ||
|
|
70aca302a8 | ||
|
|
4a11d3a924 | ||
|
|
cbef3ea79d | ||
|
|
9f926a0322 | ||
|
|
9b4bd1bcd9 | ||
|
|
3ff21ab8f0 | ||
|
|
038b8f2685 | ||
|
|
dfb8a3eccc | ||
|
|
f31eb48556 | ||
|
|
71affea1fb | ||
|
|
4b807a6f7f | ||
|
|
1be2362045 | ||
|
|
51f0b72fd9 | ||
|
|
d43c724cd5 | ||
|
|
d542a56ed7 | ||
|
|
76f03b124d | ||
|
|
637f5f95f1 | ||
|
|
ec6a9a96ec | ||
|
|
41b7174628 | ||
|
|
ec568adef4 | ||
|
|
b0223ddaa9 | ||
|
|
94d47954cd | ||
|
|
60df390c1b | ||
|
|
d7e93bbe14 | ||
|
|
c087522e0a | ||
|
|
c8e45b62dd | ||
|
|
9aa3946c06 | ||
|
|
9153541837 | ||
|
|
5b43b97ce2 | ||
|
|
ff522d1e79 | ||
|
|
1f21da9b19 | ||
|
|
cb8603c2f8 | ||
|
|
1cdbf5ee9a | ||
|
|
b698f4337d | ||
|
|
1f69a746c9 | ||
|
|
c94b375aec | ||
|
|
8a12219565 | ||
|
|
3e8def09d6 | ||
|
|
aa807bf51d | ||
|
|
d37faa57dc | ||
|
|
2c35b31599 | ||
|
|
ea6406e38f | ||
|
|
0aa95ef948 | ||
|
|
d35ff03237 | ||
|
|
8a6067d653 | ||
|
|
4e1ca02189 | ||
|
|
232b7ce3ea | ||
|
|
14618526a7 | ||
|
|
81bbde43ab | ||
|
|
7a89f0b85e | ||
|
|
81a48222ad | ||
|
|
048dfa839c | ||
|
|
15ce0f5c4b | ||
|
|
8a6d597984 | ||
|
|
aaae75eb9b | ||
|
|
9a94618799 | ||
|
|
7b77fc85d9 | ||
|
|
48c4abb60a | ||
|
|
589055732e | ||
|
|
a1a03c4c01 | ||
|
|
a97150d45a | ||
|
|
f46446ae37 | ||
|
|
4e3b7e4683 | ||
|
|
45b3b17ac7 | ||
|
|
feaab79d35 | ||
|
|
06fdd45c36 | ||
|
|
25efd5ef03 | ||
|
|
8fa1f1ef61 | ||
|
|
7f98291e12 | ||
|
|
4cc7e91722 | ||
|
|
cfa87fc674 |
@@ -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.
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# $CleanupPath must be set by RMM
|
||||||
|
# $DaysWithNoModification must be set by RMM
|
||||||
|
|
||||||
|
# Exit if the source path does not exist
|
||||||
|
If ( !(Test-Path $CleanupPath) ) { Write-Output "The specified path does not exist: ""${CleanupPath}""" ; Return }
|
||||||
|
|
||||||
|
# Running total of the size of all directories and files
|
||||||
|
$TotalSize = 0
|
||||||
|
|
||||||
|
# Running total of the number of all identified directories
|
||||||
|
$TotalDirectories = 0
|
||||||
|
|
||||||
|
# Running total of the number of all identified files
|
||||||
|
$TotalFiles = 0
|
||||||
|
|
||||||
|
$StartDate = Get-Date
|
||||||
|
$StartTime = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||||
|
Write-Output '-------------------------------------------------------------------------------'
|
||||||
|
Write-Output "Started at : ${StartTime}"
|
||||||
|
Write-Output "Run on : ${Env:COMPUTERNAME}"
|
||||||
|
Write-Output "Source : ${CleanupPath}"
|
||||||
|
If ( $DaysWithNoModification -gt 0 ) { Write-Output "Modified : ${DaysWithNoModification} day(s) ago" }
|
||||||
|
Write-Output '-------------------------------------------------------------------------------'
|
||||||
|
|
||||||
|
# Identify all files and folders that match the criteria for what we're looking for
|
||||||
|
$TEMPORARY_REVIT_OBJECTS = Get-ChildItem -Path $CleanupPath -Recurse | Where-Object {
|
||||||
|
(
|
||||||
|
# Revit model backup folder
|
||||||
|
$_.Name -match '_backup$' -or
|
||||||
|
|
||||||
|
# Revit backup models
|
||||||
|
$_.Name -match '\.\d\d\d\d\.rvt$'
|
||||||
|
|
||||||
|
# Files and folders not recently modified
|
||||||
|
) -and ($_.LastWriteTime -lt $StartDate.AddDays(-$DaysWithNoModification))
|
||||||
|
}
|
||||||
|
|
||||||
|
# Loop thru each path to get its size and
|
||||||
|
Foreach ( $obj in $TEMPORARY_REVIT_OBJECTS ) {
|
||||||
|
|
||||||
|
# Skip the object if it no longer exists or is otherwise inaccessible
|
||||||
|
If ( !(Test-Path $obj.FullName) ) { Continue } Else { $item = $obj.FullName }
|
||||||
|
|
||||||
|
Switch ( $obj.PSIsContainer ) {
|
||||||
|
|
||||||
|
# Get the size of the directory and update counter
|
||||||
|
$true {
|
||||||
|
$size = (Get-ChildItem -Path $item -Recurse | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
|
||||||
|
$TotalDirectories++
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the size of the file and update counter
|
||||||
|
$false {
|
||||||
|
$size = (Get-Item $item | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
|
||||||
|
$TotalFiles++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Output $item
|
||||||
|
$TotalSize += $size
|
||||||
|
}
|
||||||
|
|
||||||
|
# Record stats
|
||||||
|
$EndTime = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||||
|
$Duration = "{0:g}" -f (New-TimeSpan -Start $StartTime -End $EndTime)
|
||||||
|
Switch ( $TotalSize ) {
|
||||||
|
{ $_ -ge 1TB } { $GrandTotal = "{0}TB" -f ([Math]::Round(($TotalSize / 1TB),2)) ; break }
|
||||||
|
{ $_ -ge 1GB } { $GrandTotal = "{0}GB" -f ([Math]::Round(($TotalSize / 1GB),2)) ; break }
|
||||||
|
{ $_ -ge 1MB } { $GrandTotal = "{0}MB" -f ([Math]::Round(($TotalSize / 1MB),2)) ; break }
|
||||||
|
{ $_ -ge 1KB } { $GrandTotal = "{0}KB" -f ([Math]::Round(($TotalSize / 1KB),2)) ; break }
|
||||||
|
{ $_ -lt 1KB } { $GrandTotal = "${TotalSize} bytes" ; break }
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output '-------------------------------------------------------------------------------'
|
||||||
|
Write-Output "Finished at: ${EndTime}"
|
||||||
|
Write-Output "Duration : ${Duration}"
|
||||||
|
Write-Output "Directories: ${TotalDirectories}"
|
||||||
|
Write-Output "Files : ${TotalFiles}"
|
||||||
|
Write-Output "Total size : ${GrandTotal}"
|
||||||
|
Write-Output '-------------------------------------------------------------------------------'
|
||||||
+44
-24
@@ -39,24 +39,23 @@ If ( $CBSLogs ) {
|
|||||||
$OldTempFiles = Get-ChildItem -Path "${Env:SystemRoot}\TEMP" | Where-Object { $_.LastWriteTime -lt ((Get-Date) - $TimeDelta) }
|
$OldTempFiles = Get-ChildItem -Path "${Env:SystemRoot}\TEMP" | Where-Object { $_.LastWriteTime -lt ((Get-Date) - $TimeDelta) }
|
||||||
If ( $OldTempFiles ) {
|
If ( $OldTempFiles ) {
|
||||||
Write-Output "Deleting all temp files not modified in ${OlderThan} day(s) from ${Env:SystemRoot}\TEMP"
|
Write-Output "Deleting all temp files not modified in ${OlderThan} day(s) from ${Env:SystemRoot}\TEMP"
|
||||||
ForEach ( $OldTempFile in $OldTempFiles ) { Remove-Item $OldTempFile -Force -Recurse -ErrorAction SilentlyContinue }
|
ForEach ( $OldTempFile in $OldTempFiles ) { Remove-Item $OldTempFile.FullName -Force -Recurse -ErrorAction SilentlyContinue }
|
||||||
}
|
}
|
||||||
|
|
||||||
# Remove all old files from the Recycle Bin
|
# Remove all old files from the Recycle Bin
|
||||||
$RecycleBin = (New-Object -ComObject Shell.Application).NameSpace(0xa)
|
$UserRecycleBins = Get-ChildItem -Path "${Env:SystemDrive}\`$Recycle.Bin" -Directory -ErrorAction SilentlyContinue
|
||||||
ForEach($file in $RecycleBin.Items()) {
|
If ( ($UserRecycleBins | Measure-Object).Count -gt 0 ) {
|
||||||
|
Write-Output "Deleting all recycle bin items older than ${OlderThan} days from:"
|
||||||
# Remove RTL and LTR marks from date string so it can be compared
|
ForEach ( $userRecycleBin in $UserRecycleBins ) {
|
||||||
$DateDeleted = $Recycler.GetDetailsOf($item,2) -replace "\u200f|\u200e",""
|
$userRecycleBinFullName = $userRecycleBin.FullName
|
||||||
|
Write-Output " - $userRecycleBinFullName"
|
||||||
# Make sure the item has expired
|
$items = Get-ChildItem -Path $userRecycleBin.FullName -Recurse -ErrorAction SilentlyContinue
|
||||||
If( (Get-Date $DateDeleted) -lt ((Get-Date) - $TimeDelta)) {
|
ForEach ( $item in $items ) {
|
||||||
|
If ( $item.LastWriteTime -lt $TimeDelta ) {
|
||||||
# Delete the item
|
Try { Remove-Item -Path $item.FullName -Force -ErrorAction Continue }
|
||||||
$path = $file.Path
|
Catch { Write-Error $_.Exception.Message }
|
||||||
Write-Output "Deleting expired Recycle Bin item: ""${path}"""
|
}
|
||||||
Try { Remove-Item -Path $path -Force -Recurse }
|
}
|
||||||
Catch { Write-Error $_.Exception.Message }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +70,7 @@ If ( Test-Path "${Env:SystemDrive}\hiberfil.sys" ) {
|
|||||||
$PDCRevocationDumpFiles = Get-ChildItem -Path "${Env:SystemRoot}\LiveKernelReports\*" -File -Include "PDCRevocation-*.dmp"
|
$PDCRevocationDumpFiles = Get-ChildItem -Path "${Env:SystemRoot}\LiveKernelReports\*" -File -Include "PDCRevocation-*.dmp"
|
||||||
If ( $PDCRevocationDumpFiles ) {
|
If ( $PDCRevocationDumpFiles ) {
|
||||||
Write-Output "Deleting PDCRevocation dump files from ${Env:SystemRoot}\LiveKernelReports"
|
Write-Output "Deleting PDCRevocation dump files from ${Env:SystemRoot}\LiveKernelReports"
|
||||||
ForEach ( $PDCRevocationDump in $PDCRevocationDumpFiles ) { Remove-Item $PDCRevocationDump -Force -ErrorAction SilentlyContinue }
|
ForEach ( $PDCRevocationDump in $PDCRevocationDumpFiles ) { Remove-Item $PDCRevocationDump.FullName -Force -ErrorAction SilentlyContinue }
|
||||||
}
|
}
|
||||||
|
|
||||||
# Remove Genetec application crash dumps
|
# Remove Genetec application crash dumps
|
||||||
@@ -108,25 +107,46 @@ If ( Test-Path $ESDB ) {
|
|||||||
# Remove any Autodesk installers at their default location
|
# Remove any Autodesk installers at their default location
|
||||||
$ADSKInst = "${Env:SystemDrive}\Autodesk"
|
$ADSKInst = "${Env:SystemDrive}\Autodesk"
|
||||||
If ( Test-Path $ADSKInst ) {
|
If ( Test-Path $ADSKInst ) {
|
||||||
$foldersToDelete = Get-ChildItem -Path $ADKSInst -ErrorAction SilentlyContinue | Where-Object { ($_.PSIsContainer) -and ($_.LastWriteTime -lt ((Get-Date) - $TimeDelta)) -and ( $_.Name -ne "WI") }
|
$foldersToDelete = Get-ChildItem -Path $ADSKInst -ErrorAction SilentlyContinue | Where-Object { ($_.PSIsContainer) -and ($_.LastWriteTime -lt ((Get-Date) - $TimeDelta)) -and ( $_.Name -ne "WI") }
|
||||||
ForEach ( $folder in $foldersToDelete ) {
|
ForEach ( $f in $foldersToDelete ) {
|
||||||
$path = $folder.FullName
|
$path = $f.FullName
|
||||||
Write-Output "Deleting Autodesk installer directory: ""${path}"""
|
Write-Output "Deleting Autodesk installer directory: ""${path}"""
|
||||||
Try { Remove-Item $folder -Force -Recurse -ErrorAction SilentlyContinue }
|
Try { Remove-Item $f.FullName -Force -Recurse -ErrorAction SilentlyContinue }
|
||||||
Catch { Write-Error $_.Exception.Message }
|
Catch { Write-Error $_.Exception.Message }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# TODO: Evaluate system restore points and delete them if they take up too much space
|
# Delete all but most recent shadow copy of system drive
|
||||||
|
$SystemVolume = Get-CimInstance -ClassName Win32_Volume -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "${Env:SystemDrive}\" }
|
||||||
|
$ShadowCopies = Get-CimInstance -ClassName Win32_ShadowCopy -ErrorAction SilentlyContinue | Where-Object { $_.VolumeName -like $SystemVolume.DeviceID }
|
||||||
|
If ( $ShadowCopies.Count -gt 1 ) {
|
||||||
|
Write-Output "Deleting all of the oldest shadow copies of ${Env:SystemDrive}"
|
||||||
|
$ShadowsToDelete = $ShadowCopies.Count - 1
|
||||||
|
While ( $ShadowsToDelete -gt 0 ) {
|
||||||
|
vssadmin delete shadows /For=$Env:SystemDrive /Oldest /Quiet
|
||||||
|
$ShadowsToDelete = $ShadowsToDelete - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Delete old temp directories from Chocolatey cache
|
||||||
|
$ChocolateyCache = "${Env:WinDir}\Temp\chocolatey"
|
||||||
|
$ChocolateyTempDirs = Get-ChildItem -Path $ChocolateyCache -Directory -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -lt ((Get-Date) - $TimeDelta) }
|
||||||
|
If ( $ChocolateyTempDirs ) {
|
||||||
|
Write-Output "Deleting all temp directories not modified in ${OlderThan} day(s) from ""${ChocolateyCache}"""
|
||||||
|
ForEach ( $d in $ChocolateyTempDirs ) {
|
||||||
|
Try { Remove-Item $d.FullName -Force -Recurse -ErrorAction SilentlyContinue }
|
||||||
|
Catch { Write-Error $_.Exception.Message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Remove files from user profile directories
|
# Remove files from user profile directories
|
||||||
ForEach ( $userProfile in $(EnumUserProfiles) ) {
|
ForEach ( $userProfile in $(Get-UserProfiles) ) {
|
||||||
|
|
||||||
# Remove all user temp files older than 7 days
|
# Remove all user temp files older than 7 days
|
||||||
$OldTempFiles = Get-ChildItem -Path "${userProfile}\AppData\Local\Temp" -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -lt ((Get-Date) - $TimeDelta) }
|
$OldTempFiles = Get-ChildItem -Path "${userProfile}\AppData\Local\Temp" -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -lt ((Get-Date) - $TimeDelta) }
|
||||||
If ( $OldTempFiles ) {
|
If ( $OldTempFiles ) {
|
||||||
Write-Output "Deleting all temp files not modified in ${OlderThan} day(s) from ""${userProfile}\AppData\Local\Temp"""
|
Write-Output "Deleting all temp files not modified in ${OlderThan} day(s) from ""${userProfile}\AppData\Local\Temp"""
|
||||||
ForEach ( $OldTempFile in $OldTempFiles ) { Remove-Item $OldTempFile -Force -Recurse -ErrorAction SilentlyContinue }
|
ForEach ( $OldTempFile in $OldTempFiles ) { Remove-Item $OldTempFile.FullName -Force -Recurse -ErrorAction SilentlyContinue }
|
||||||
}
|
}
|
||||||
|
|
||||||
# Remove AutoCAD temp files
|
# Remove AutoCAD temp files
|
||||||
@@ -135,7 +155,7 @@ ForEach ( $userProfile in $(EnumUserProfiles) ) {
|
|||||||
If ( $AutoCADTempFiles ) {
|
If ( $AutoCADTempFiles ) {
|
||||||
Write-Output "Deleting AutoCAD temp files"
|
Write-Output "Deleting AutoCAD temp files"
|
||||||
ForEach ( $tempfile in $AutoCADTempFiles ) {
|
ForEach ( $tempfile in $AutoCADTempFiles ) {
|
||||||
Try { Remove-Item $tempfile -Force -ErrorAction SilentlyContinue }
|
Try { Remove-Item $tempfile.FullName -Force -ErrorAction SilentlyContinue }
|
||||||
Catch { Write-Error $_.Exception.Message }
|
Catch { Write-Error $_.Exception.Message }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
$LogsToEnable = @(
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
$LogsToDisable = @(
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
ForEach ( $log in $LogsToEnable ) {
|
||||||
|
Enable-Log -Name $log
|
||||||
|
}
|
||||||
|
|
||||||
|
ForEach ( $log in $LogsToDisable) {
|
||||||
|
Disable-Log -Name $log
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# Exit this script if the current device doesn't have a built-in battery
|
||||||
|
If ( $null -eq (Get-CimInstance -ClassName Win32_Battery -ErrorAction SilentlyContinue) ) { Microsoft.Powershell.Utility\Write-Output "This endpoint does not have a battery" ; Exit }
|
||||||
|
|
||||||
|
# Define file paths
|
||||||
|
$Today = Get-LongDate
|
||||||
|
$ReportPathNoExt = "${OutputDirectory}\batteryreport_${Env:COMPUTERNAME}_${Today}"
|
||||||
|
$HtmlReportPath = $ReportPathNoExt + '.html'
|
||||||
|
$XmlReportPath = $ReportPathNoExt + '.xml'
|
||||||
|
|
||||||
|
# Delete reports if they already exist
|
||||||
|
If ( Test-Path $XmlReportPath ) { Remove-Item -Path $XmlReportPath -Force }
|
||||||
|
If ( Test-Path $HtmlReportPath ) { Remove-Item -Path $HtmlReportPath -Force }
|
||||||
|
|
||||||
|
# Generate the XML report
|
||||||
|
Write-Output "Generating XML battery report: ${XmlReportPath}"
|
||||||
|
|
||||||
|
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
|
||||||
|
Write-Output "Generating HTML battery report: ${HtmlReportPath}"
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Eval the alert data
|
||||||
|
If ( -not $AlertData ) { Write-Error "No alert data provided" ; Return } Else { Write-Output $AlertData ; Return }
|
||||||
|
|
||||||
|
## Create ticket
|
||||||
|
#New-RMMTicket -Subject "App Crash on ${Env:COMPUTERNAME}" -InitialIssue "Application crash detected. Reason for crash indicates a Windows component is at fault."
|
||||||
|
#
|
||||||
|
## Update the ticket
|
||||||
|
#$TicketComment = "It looks like an app crashed on your computer, and the reason for the crash indicates a Windows component is at fault.`n`nI'll repair your system now."
|
||||||
|
#Create-Syncro-Ticket-Comment -TicketIdOrNumber $TicketID -Subject "Update" -Body $TicketComment -Hidden $false -DoNotEmail $false
|
||||||
|
#
|
||||||
|
## Bill time to the ticket
|
||||||
|
#$BillableTimeInMinutes = 15
|
||||||
|
#$StartAt = (Get-Date).AddMinutes(-$BillableTimeInMinutes).ToString("o")
|
||||||
|
#$TimeEntryNote = "Analysis indicates crash caused by Windows component(s). Scanning and repairing corruption in the system."
|
||||||
|
#Create-Syncro-Ticket-TimerEntry -TicketIdOrNumber $TicketID -StartTime $StartAt -DurationMinutes $BillableTimeInMinutes -Notes $TimeEntryNote -UserIdOrEmail $TimeAttributedToUser -ChargeTime "true"
|
||||||
|
#
|
||||||
|
## Fix Windows Health
|
||||||
|
#$FixWindowsHealth = Download-File -URL 'https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Fix-WindowsHealth.ps1'
|
||||||
|
#. $FixWindowsHealth
|
||||||
|
#If ( Test-Path $FixWindowsHealth ) { Remove-Item $FixWindowsHealth -Force -ErrorAction SilentlyContinue }
|
||||||
|
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
If ( Is64Bit ) { $BlueScreenView = $Global:ToolsDirectory + '\BlueScreenView-x64.exe' } Else { $BlueScreenView = $Global:ToolsDirectory + '\BlueScreenView-x86.exe' }
|
||||||
|
If ( !(Test-Path $BlueScreenView) ) { Write-Error "File does not exist: ${BlueScreenView}" ; Return }
|
||||||
|
|
||||||
|
# Remove existing BSOD report
|
||||||
|
$BSODReport = $Global:OutputDirectory + '\BSODReport.txt'
|
||||||
|
If ( Test-Path $BSODReport ) { Write-Output "Deleting existing BSOD report: ${BSODReport}" ; Remove-Item $BSODReport -Force -ErrorAction SilentlyContinue }
|
||||||
|
|
||||||
|
# Run BlueScreenView
|
||||||
|
Start-Process $BlueScreenView -ArgumentList "/stext ${BSODReport} /sort ~1" -Wait
|
||||||
|
|
||||||
|
# Wait for BSOD report
|
||||||
|
For ($i = 0; $i -lt 1200; $i++) {
|
||||||
|
If ( Test-Path $BSODReport ) { Break }
|
||||||
|
Start-Sleep -Seconds 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Exit if the BSOD report was not created
|
||||||
|
If ( !(Test-Path $BSODReport) ) { Write-Output "BSOD report was not found: ${BSODReport}" ; Return }
|
||||||
|
|
||||||
|
# Exit if the BSOD report is empty
|
||||||
|
If ( (Get-Content -Path $BSODReport).Length -eq 0 ) {
|
||||||
|
Write-Output "BSOD report is empty: ${BSODReport}"
|
||||||
|
Remove-Item $BSODReport -Force
|
||||||
|
Return
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create ticket
|
||||||
|
$BSODReportContent = $(Get-Content -Path $BSODReport) -join "`n"
|
||||||
|
New-RMMTicket -Subject "System Crash on ${Env:COMPUTERNAME}" -InitialIssue $BSODReportContent
|
||||||
|
|
||||||
|
# Update the ticket
|
||||||
|
New-TicketComment -Comment "It looks like your computer crashed. I'll get this resolved as soon as possible."
|
||||||
|
|
||||||
|
# Wait a bit
|
||||||
|
Start-Sleep -Seconds 120
|
||||||
|
|
||||||
|
# Setup providers for matching BSOD report content
|
||||||
|
$ProviderMicrosoft = 'Microsoft|Windows'
|
||||||
|
|
||||||
|
# Get the provider data from the most recent BSOD
|
||||||
|
$CulpritProvider = $BSODReportContent[12..13] -join '`n'
|
||||||
|
|
||||||
|
Switch ( $CulpritProvider ) {
|
||||||
|
{ $_ -match $ProviderMicrosoft } {
|
||||||
|
New-TicketComment -Comment "I've analyzed the crash report and it indicates a problem with a Windows component. I'll repair the system now."
|
||||||
|
Source-PSScript -ScriptName "Fix-WindowsHealth"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Exit if the endpoint is a server edition
|
||||||
|
Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object -Property ProductType | ForEach-Object { If ( $_.ProductType -gt 1 ) { Return } }
|
||||||
|
|
||||||
|
# Exit if the endpoint is not running at least Windows 10
|
||||||
|
If ( IsWindowsVersion -lt "10.0" ) { Return }
|
||||||
|
|
||||||
|
# Exit if the endpoint is not running the Pro/Enterprise version of Windows
|
||||||
|
$Edition = ($(systeminfo | findstr /B /C:"OS Name") -replace "OS Name:","").Trim()
|
||||||
|
If ( ($Edition -notmatch "Pro") -and ($Edition -notmatch "Enterprise") ) { Return }
|
||||||
|
|
||||||
|
# Exit if Hyper-V is already enabled
|
||||||
|
If ( (Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V).State -eq "Enabled" ) { Return }
|
||||||
|
|
||||||
|
# Endpoint is running the correct version of Windows, enabling Hyper-V features
|
||||||
|
Write-Output "Enabling Hyper-V features"
|
||||||
|
Try { Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All -NoRestart }
|
||||||
|
Catch { Write-Error $_.Exception.Message }
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
#. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
# https://knowledge.autodesk.com/search-result/caas/downloads/content/licensing-support-tool.html
|
# https://www.autodesk.com/support/technical/article/caas/tsarticles/ts/6IFPpIksTDfsCzp1JwIH5k.html
|
||||||
$URL = 'https://knowledge.autodesk.com/sites/default/files/file_downloads/AdskLicensingSupportTool-2.2.0.502-win.zip'
|
$URL = 'https://damassets.autodesk.net/content/dam/autodesk/external-assets/support-articles/licensing-support-tool/AdskLicensingSupportTool-2.3.0.692-win.zip'
|
||||||
|
|
||||||
# Download the zip archive
|
# Download the zip archive
|
||||||
$LicensingSupportTool = Download-File -URL $URL
|
$LicensingSupportTool = Download-File -URL $URL
|
||||||
|
|
||||||
# Extract the downloaded zip file
|
# Extract the downloaded zip file
|
||||||
Unzip-Object $LicensingSupportTool
|
Expand-Archive -Path $LicensingSupportTool
|
||||||
|
|
||||||
$WorkingDir = '{0}\{1}' -f (Split-Path -Path $LicensingSupportTool -Parent), "AdskLicensingSupportTool"
|
$WorkingDir = '{0}\{1}' -f (Split-Path -Path $LicensingSupportTool -Parent), "AdskLicensingSupportTool"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
@echo off
|
||||||
|
if /I %0 EQU "%~dpnx0" (set LAUNCH=1) else (set LAUNCH=0)
|
||||||
|
set PROP=ExcludeExplicitO365Endpoint
|
||||||
|
set KEY=Microsoft\Office\16.0\Outlook\AutoDiscover
|
||||||
|
echo Disabling Office 365 AutoDiscover...
|
||||||
|
reg add HKCU\Software\%KEY% /v %PROP% /t REG_DWORD /d 1 /f /reg:32 >nul 2>nul
|
||||||
|
reg add HKCU\Software\Policies\%KEY% /v %PROP% /t REG_DWORD /d 1 /f /reg:32 >nul 2>nul
|
||||||
|
reg add HKCU\Software\%KEY% /v %PROP% /t REG_DWORD /d 1 /f /reg:64 >nul 2>nul
|
||||||
|
reg add HKCU\Software\Policies\%KEY% /v %PROP% /t REG_DWORD /d 1 /f /reg:64 >nul 2>nul
|
||||||
|
if %ERRORLEVEL% GTR 0 (
|
||||||
|
if %LAUNCH% GTR 0 color 0e
|
||||||
|
echo ERROR: This script failed to run. Please make sure to Run As Administrator
|
||||||
|
reg delete HKCU\Software\%KEY% /v %PROP% /f /reg:32 >nul 2>nul
|
||||||
|
reg delete HKCU\Software\Policies\%KEY% /v %PROP% /f /reg:32 >nul 2>nul
|
||||||
|
reg delete HKCU\Software\%KEY% /v %PROP% /f /reg:64 >nul 2>nul
|
||||||
|
reg delete HKCU\Software\Policies\%KEY% /v %PROP% /f /reg:64 >nul 2>nul
|
||||||
|
echo Changes have been reverted.
|
||||||
|
if %LAUNCH% GTR 0 pause
|
||||||
|
goto :EOF
|
||||||
|
)
|
||||||
|
echo Successfully disabled Office 365 AutoDiscover!
|
||||||
|
echo Please restart your comptuer.
|
||||||
|
if %LAUNCH% GTR 0 pause
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
setlocal
|
setlocal
|
||||||
cls
|
cls
|
||||||
|
|
||||||
call :configure-service AteraAgent
|
call :configure-service Syncro
|
||||||
|
call :configure-service SyncroLive
|
||||||
|
call :configure-service SyncroOvermind
|
||||||
call :configure-service SplashtopRemoteService
|
call :configure-service SplashtopRemoteService
|
||||||
goto :EOF
|
goto :EOF
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Disable all OneDrive notifications
|
||||||
|
[Microsoft.Win32.Registry]::SetValue("HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Notifications\Settings\Microsoft.SkyDrive.Desktop","Enabled",0,[Microsoft.Win32.RegistryValueKind]::DWord)
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
@echo off
|
||||||
|
if /I %0 EQU "%~dpnx0" (set LAUNCH=1) else (set LAUNCH=0)
|
||||||
|
set PROP=HideNewOutlookToggle
|
||||||
|
set KEY=Microsoft\Office\16.0\Outlook\Options\General
|
||||||
|
set ARGS=/v %PROP% /t REG_DWORD /d 1 /f
|
||||||
|
echo Disabling New Outlook toggle...
|
||||||
|
reg add HKCU\Software\%KEY% %ARGS% /reg:32 >nul 2>nul
|
||||||
|
reg add HKCU\Software\Policies\%KEY% %ARGS% /reg:32 >nul 2>nul
|
||||||
|
reg add HKCU\Software\%KEY% %ARGS% /reg:64 >nul 2>nul
|
||||||
|
reg add HKCU\Software\Policies\%KEY% %ARGS% /reg:64 >nul 2>nul
|
||||||
|
if %ERRORLEVEL% GTR 0 (
|
||||||
|
if %LAUNCH% GTR 0 color 0e
|
||||||
|
echo ERROR: This script failed to run. Please make sure to Run As Administrator
|
||||||
|
reg delete HKCU\Software\%KEY% /v %PROP% /f /reg:32 >nul 2>nul
|
||||||
|
reg delete HKCU\Software\Policies\%KEY% /v %PROP% /f /reg:32 >nul 2>nul
|
||||||
|
reg delete HKCU\Software\%KEY% /v %PROP% /f /reg:64 >nul 2>nul
|
||||||
|
reg delete HKCU\Software\Policies\%KEY% /v %PROP% /f /reg:64 >nul 2>nul
|
||||||
|
echo Changes have been reverted.
|
||||||
|
if %LAUNCH% GTR 0 pause
|
||||||
|
goto :EOF
|
||||||
|
)
|
||||||
|
echo Successfully disabled New Outlook toggle!
|
||||||
|
echo Please restart your comptuer.
|
||||||
|
if %LAUNCH% GTR 0 pause
|
||||||
@@ -10,23 +10,25 @@ switch ( $Edition.ToLower() ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Install and import additional Powershell module
|
# Install and import additional Powershell module
|
||||||
Install-MSP360Module
|
Import-MSP360Module
|
||||||
Import-Module -Name MSP360
|
|
||||||
|
|
||||||
# Add backup user to installed product
|
# Exit this script if the agent is not installed
|
||||||
#Try {
|
If ( !($(Get-MBSAgent -ErrorAction SilentlyContinue)) ) {
|
||||||
# $MSP360Password = ConvertTo-SecureString -string $MSP360Password -AsPlainText -Force
|
Microsoft.Powershell.Utility\Write-Output "Backup agent is not installed. This script will now exit."
|
||||||
# Write-Output "Adding backup user account: ${MSP360Username}"
|
Return
|
||||||
# Add-MBSUserAccount -User $MSP360Username -Password $MSP360Password
|
}
|
||||||
#}
|
|
||||||
#Catch { Write-Error $_.Exception.Message }
|
|
||||||
|
|
||||||
# Set the agent edition
|
# Set the agent edition
|
||||||
# Note: the product edition must be set *after* the user is added
|
|
||||||
Write-Output "Setting Emberkom Cloud Backup agent edition to ""$AgentEdition"""
|
Write-Output "Setting Emberkom Cloud Backup agent edition to ""$AgentEdition"""
|
||||||
Try { Set-MBSAgentSetting -Edition $AgentEdition -Verbose }
|
Try {
|
||||||
|
Switch ([string]::IsNullOrEmpty($MasterPassword)) {
|
||||||
|
$true { Set-MBSAgentSetting -Edition $AgentEdition -Verbose }
|
||||||
|
$false { Set-MBSAgentSetting -Edition $AgentEdition -MasterPassword $(ConvertTo-SecureString -String $MasterPassword -AsPlainText -Force) -Verbose }
|
||||||
|
}
|
||||||
|
}
|
||||||
Catch { Write-Error $_.Exception.Message }
|
Catch { Write-Error $_.Exception.Message }
|
||||||
|
|
||||||
# Delete the desktop icon
|
# Delete the desktop icon
|
||||||
"${Env:PUBLIC}\Desktop\Emberkom Backup.LNK",
|
Write-Output "Removing desktop icon"
|
||||||
"${Env:PUBLIC}\Desktop\Emberkom Cloud Backup.LNK" | Remove-File
|
Remove-Item -Path "${Env:PUBLIC}\Desktop\Emberkom Backup.LNK" -Force -ErrorAction SilentlyContinue
|
||||||
|
Remove-Item -Path "${Env:PUBLIC}\Desktop\Emberkom Cloud Backup.LNK" -Force -ErrorAction SilentlyContinue
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
Stop-MemoryHog -AppName 'searchapp' -MemoryLimit 2048
|
Stop-MemoryHog -AppName 'searchapp' -MemoryLimit 2048
|
||||||
Stop-MemoryHog -AppName 'AgentPackageProgramManagement' -MemoryLimit 1024
|
#Stop-MemoryHog -AppName 'AgentPackageProgramManagement' -MemoryLimit 1024
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Note: $FileTypes must be set by the calling script
|
||||||
|
|
||||||
|
# Unblock attachment types
|
||||||
|
[Microsoft.Win32.Registry]::SetValue("HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\16.0\Outlook\Security","Level1Remove","${FileTypes}",[Microsoft.Win32.RegistryValueKind]::String)
|
||||||
@@ -6,13 +6,16 @@ set /a REBOOT=0
|
|||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo Configuring required services to automatically start in Safe Mode with Networking.
|
echo Configuring required services to automatically start in Safe Mode with Networking.
|
||||||
call :configure-service AteraAgent
|
call :configure-service Syncro
|
||||||
|
call :configure-service SyncroLive
|
||||||
|
call :configure-service SyncroOvermind
|
||||||
call :configure-service SplashtopRemoteService
|
call :configure-service SplashtopRemoteService
|
||||||
|
call :configure-service "Cloud Backup Service Remote Management"
|
||||||
|
|
||||||
:schedule-normal-reboot
|
:schedule-normal-reboot
|
||||||
call :reset-errorlevel
|
call :reset-errorlevel
|
||||||
echo Configuring system to reboot into Normal Mode at next reboot
|
echo Configuring system to reboot into Normal Mode at next reboot
|
||||||
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Runonce" /v "*RestartNormalMode" /t REG_SZ /d "%SystemRoot%\System32\bcdedit.exe /deletevalue {current} safeboot" /f > nul 2>&1
|
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Runonce" /v "*RestartNormalMode" /t REG_SZ /d "bcdedit /deletevalue {current} safeboot" /f > nul 2>&1
|
||||||
if %ERRORLEVEL% GTR 0 (
|
if %ERRORLEVEL% GTR 0 (
|
||||||
set /a REBOOT=%REBOOT%+1
|
set /a REBOOT=%REBOOT%+1
|
||||||
echo ERROR: could not configure reboot into normal mode
|
echo ERROR: could not configure reboot into normal mode
|
||||||
@@ -20,7 +23,7 @@ if %ERRORLEVEL% GTR 0 (
|
|||||||
|
|
||||||
:configure-reboot-safemodewithnetworking
|
:configure-reboot-safemodewithnetworking
|
||||||
call :reset-errorlevel
|
call :reset-errorlevel
|
||||||
"%SystemRoot%\System32\bcdedit.exe" /set {current} safeboot network
|
bcdedit /set {current} safeboot network
|
||||||
if %ERRORLEVEL% GTR 0 (
|
if %ERRORLEVEL% GTR 0 (
|
||||||
set /a REBOOT=%REBOOT%+1
|
set /a REBOOT=%REBOOT%+1
|
||||||
echo ERROR: could not restart into Safe Mode with Networking
|
echo ERROR: could not restart into Safe Mode with Networking
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# List of services to configure
|
||||||
|
$Services = @(
|
||||||
|
"Syncro",
|
||||||
|
"SyncroLive",
|
||||||
|
"SyncroOvermind",
|
||||||
|
"SplashtopRemoteService"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configure necessary services to restart in Safe Mode with Networking
|
||||||
|
$Services | ForEach-Object {
|
||||||
|
If ( Get-Service $_ -ErrorAction SilentlyContinue ) {
|
||||||
|
Write-Output "Configuring ${_} service to start in Safe Mode with Networking"
|
||||||
|
Try { New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SafeBoot\Network\${_}" -Force -ErrorAction SilentlyContinue | Out-Null }
|
||||||
|
Catch { Write-Error "ERROR: could not configure ${_} service to start in Safe Mode with Networking" ; Return }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Configure system to reboot into Normal Mode at next reboot
|
||||||
|
Write-Output "Configuring system to reboot into Normal Mode at next reboot"
|
||||||
|
Try { [Microsoft.Win32.Registry]::SetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Runonce",'*RestartNormalMode','cmd.exe /c "%SystemRoot%\System32\bcdedit.exe" /deletevalue {default} safeboot',[Microsoft.Win32.RegistryValueKind]::String) }
|
||||||
|
Catch { Write-Error "ERROR: could not configure reboot into normal mode" ; Return }
|
||||||
|
|
||||||
|
# Configure reboot into Safe Mode with Networking
|
||||||
|
Write-Output "Configuring system to reboot into Safe Mode with Networking"
|
||||||
|
$command = "${Env:SystemRoot}\System32\bcdedit.exe"
|
||||||
|
$params = "/set {default} safeboot network".Split(" ")
|
||||||
|
Try { & "${command}" $params }
|
||||||
|
Catch { Write-Error "ERROR: could not configure reboot into safe mode with networking" ; Return }
|
||||||
|
|
||||||
|
# Reboot into Safe Mode with Networking
|
||||||
|
Write-Output "Rebooting into Safe Mode with Networking"
|
||||||
|
& "${Env:SystemRoot}\System32\shutdown.exe" -r -f -t 5
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
$DNSHelper = $Global:ToolsDirectory + '\ekdns.exe'
|
||||||
|
|
||||||
|
$DNSHelperRequiredVersion = '2.3.0'
|
||||||
|
$DownloadRequired = $false
|
||||||
|
|
||||||
|
If (Test-Path $DNSHelper) {
|
||||||
|
$versionOutput = (& $DNSHelper 2>&1 | Select-Object -First 1)
|
||||||
|
If ($versionOutput -match 'v(\d+\.\d+\.\d+)') {
|
||||||
|
If ($Matches[1] -ne $DNSHelperRequiredVersion) {
|
||||||
|
$DownloadRequired = $true
|
||||||
|
}
|
||||||
|
} Else {
|
||||||
|
$DownloadRequired = $true
|
||||||
|
}
|
||||||
|
} Else {
|
||||||
|
$DownloadRequired = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Download the latest version of ekDNSHelper
|
||||||
|
If ($DownloadRequired) {
|
||||||
|
$DNSHelperURL = 'https://xfer.emberkom.com/shares/tools/folders/9b9988fb-c53a-4833-a4ad-68d01a3021bb/files/ekdns-x64-exe/download/ekdns-x64.exe'
|
||||||
|
If (!(Test-Path ${Env:ProgramFiles(x86)})) {
|
||||||
|
$DNSHelperURL = 'https://xfer.emberkom.com/shares/tools/folders/9b9988fb-c53a-4833-a4ad-68d01a3021bb/files/ekdns-x86-exe/download/ekdns-x86.exe'
|
||||||
|
}
|
||||||
|
If (Test-Path $DNSHelper) { Remove-Item -Path $DNSHelper -Force -ErrorAction SilentlyContinue }
|
||||||
|
Write-Output "Downloading latest version of ekDNSHelper"
|
||||||
|
Download-File -URL $DNSHelperURL -File $DNSHelper
|
||||||
|
}
|
||||||
|
|
||||||
|
# Delete the old version of ekDNSHelper if it exists
|
||||||
|
If ( Test-Path ($Global:ToolsDirectory + '\dnshelper.exe') ) {
|
||||||
|
Write-Output "Deleting old version of ekDNSHelper: ${Global:ToolsDirectory}\dnshelper.exe"
|
||||||
|
Remove-Item -Path ($Global:ToolsDirectory + '\dnshelper.exe') -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
$RefreshScript = $Global:ToolsDirectory + '\refresh-local-hosts.bat'
|
||||||
|
$todaysDate = Get-LongDate
|
||||||
|
$RefreshScriptHeader = @"
|
||||||
|
@echo off
|
||||||
|
REM Refresh IP to name mappings in local hosts file
|
||||||
|
REM This script was created by an automatic process and should not be edited manually
|
||||||
|
REM Author: David Yoder, Emberkom LLC (Fix-UpdateLocalHosts.ps1)
|
||||||
|
REM Updated: ${todaysDate}`n
|
||||||
|
"@
|
||||||
|
|
||||||
|
$Hosts = @(
|
||||||
|
'auth.services.mspbackups.com',
|
||||||
|
'ws.mspbackups.com'
|
||||||
|
)
|
||||||
|
|
||||||
|
If ($BackupBucket.Length -ge 2 ) {
|
||||||
|
$Hosts += $BackupBucket
|
||||||
|
}
|
||||||
|
|
||||||
|
$HostString = ''
|
||||||
|
ForEach ($Hostname in $Hosts) {
|
||||||
|
If ($HostString.Length -gt 0) { $HostString = $HostString + ',' + $Hostname }
|
||||||
|
Else { $HostString = $Hostname }
|
||||||
|
}
|
||||||
|
|
||||||
|
If ( Test-Path $DNSHelper ) {
|
||||||
|
$commandString = """${DNSHelper}"" add -host ${HostString}"
|
||||||
|
$content = $RefreshScriptHeader + $commandString
|
||||||
|
Write-Output "Creating latest refresh script"
|
||||||
|
If ( Test-Path $RefreshScript ) { Remove-Item -Path $RefreshScript -Force }
|
||||||
|
Try {
|
||||||
|
New-Item -Path $RefreshScript -ItemType File -Force | Out-Null
|
||||||
|
Set-Content -Path $RefreshScript -Value $content
|
||||||
|
}
|
||||||
|
Catch { Write-Error $_.Exception.Message }
|
||||||
|
|
||||||
|
Write-Output "Updating local hosts file with name-to-IP mappings for the following host(s): ${HostString}"
|
||||||
|
& "${RefreshScript}"
|
||||||
|
} Else {
|
||||||
|
Write-Error "Cannot find the DNS Helper executable: ""${DNSHelper}"""
|
||||||
|
}
|
||||||
+36
-10
@@ -1,24 +1,42 @@
|
|||||||
# Run SFC to check the filesystem, convert the result from Unicode to UTF8, delete the intermediate $tempFile
|
# Run SFC to check the filesystem, convert the result from Unicode to UTF8, delete the intermediate $tempFile
|
||||||
$tempFile = Get-RandomTempFile
|
$tempFile = New-TemporaryFile
|
||||||
$(sfc.exe /scannow) | Out-File $tempFile
|
$(sfc.exe /scannow) | Out-File $tempFile
|
||||||
Set-Content -Path $tempFile -Value $(Get-Content -Path $tempFile -Encoding Unicode) -Encoding UTF8
|
Set-Content -Path $tempFile -Value $(Get-Content -Path $tempFile -Encoding Unicode) -Encoding UTF8
|
||||||
$SFCOutput = Get-Content -Path $tempFile
|
$SFCOutput = Get-Content -Path $tempFile
|
||||||
|
$TicketComment = "SFC Output:`n"
|
||||||
|
ForEach ( $line in $SFCOutput ) {
|
||||||
|
If ( $line -notmatch 'Verification \d{1,2}% complete' ) { $TicketComment = $TicketComment + $line + "`n" }
|
||||||
|
}
|
||||||
|
New-TicketComment -Comment $TicketComment -Hidden -DoNotEmail
|
||||||
Remove-Item $tempFile -Force -ErrorAction SilentlyContinue
|
Remove-Item $tempFile -Force -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
# Examine $SFCOutput for integrity violations, if found make sure chkdsk runs at the next boot
|
# Examine $SFCOutput for integrity violations, if found make sure chkdsk runs at the next boot
|
||||||
If ( $SFCOutput -contains 'Windows Resource Protection did not find any integrity violations.' ) { $ScheduleCheckDisk = $false } Else { $ScheduleCheckDisk = $true }
|
Switch ( $SFCOutput ) {
|
||||||
|
{ $_ -match 'Windows Resource Protection found integrity violations' } { $ScheduleCheckDisk = $true }
|
||||||
|
{ $_ -match 'Windows Resource Protection did not find any integrity violations.' } { $ScheduleCheckDisk = $false }
|
||||||
|
{ $_ -match 'There is a system repair pending which requires reboot to complete.' } {
|
||||||
|
$ScheduleCheckDisk = $false
|
||||||
|
New-TicketComment -Comment "System repair is already pending. Reboot needs to occur first, then SFC should be run again." -Hidden -DoNotEmail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Run enhanced DISM for Windows 8 and higher endpoints
|
# Run enhanced DISM for Windows 8 and higher endpoints
|
||||||
If ( IsWindowsVersion -ge "6.2" )
|
$DISMRun = $false
|
||||||
{
|
If ( IsWindowsVersion -ge "6.2" ) {
|
||||||
|
|
||||||
# Record start of DISM repair
|
# Record start of DISM repair
|
||||||
$BeginDISMRun = (Get-Date)
|
$BeginDISMRun = (Get-Date)
|
||||||
|
|
||||||
Write-Output "Beginning repair of Windows Component Store"
|
Try {
|
||||||
Start-Process "dism.exe" -ArgumentList "/online /cleanup-image /restorehealth" -Wait
|
Write-Output "Beginning repair of Windows Component Store"
|
||||||
|
Start-Process "dism.exe" -ArgumentList "/online /cleanup-image /restorehealth" -Wait
|
||||||
|
|
||||||
Write-Output "Beginning cleanup of superceded components in Windows Component Store"
|
Write-Output "Beginning cleanup of superceded components in Windows Component Store"
|
||||||
Start-Process "dism.exe" -ArgumentList "/online /cleanup-image /startcomponentcleanup" -Wait
|
Start-Process "dism.exe" -ArgumentList "/online /cleanup-image /startcomponentcleanup" -Wait
|
||||||
|
|
||||||
|
$DISMRun = $true
|
||||||
|
}
|
||||||
|
Catch { Write-Error $_.Exception.Message }
|
||||||
|
|
||||||
# Record end of DISM repair
|
# Record end of DISM repair
|
||||||
$EndDISMRun = (Get-Date)
|
$EndDISMRun = (Get-Date)
|
||||||
@@ -26,8 +44,8 @@ If ( IsWindowsVersion -ge "6.2" )
|
|||||||
# Extract results of DISM repair from system log and add them to this log
|
# Extract results of DISM repair from system log and add them to this log
|
||||||
Write-Output "Beginning Windows Component Store log data collection"
|
Write-Output "Beginning Windows Component Store log data collection"
|
||||||
Get-Content -Path "${Env:WinDir}\Logs\DISM\dism.log" | Select-String -Pattern '^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})' |
|
Get-Content -Path "${Env:WinDir}\Logs\DISM\dism.log" | Select-String -Pattern '^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})' |
|
||||||
ForEach-Object {
|
ForEach-Object {
|
||||||
If ( ((Get-Date $_.Line.Substring(0,19)) -gt $BeginDISMRun) -and ((Get-Date $_.Line.Substring(0,19)) -le $EndDISMRun) ) {
|
If ( ((Get-Date $_.Line.Substring(0,19)) -gt $BeginDISMRun) -and ((Get-Date $_.Line.Substring(0,19)) -le $EndDISMRun) ) {
|
||||||
Write-Output $_.Line
|
Write-Output $_.Line
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,4 +57,12 @@ Else { Write-Output "Online image cleanup and restoration is only supported on W
|
|||||||
If ( $ScheduleCheckDisk ) {
|
If ( $ScheduleCheckDisk ) {
|
||||||
Write-Output "Scheduling disk repair at next boot"
|
Write-Output "Scheduling disk repair at next boot"
|
||||||
Start-Process "cmd.exe" -ArgumentList '/C echo y | chkdsk /F /R /X' -Wait
|
Start-Process "cmd.exe" -ArgumentList '/C echo y | chkdsk /F /R /X' -Wait
|
||||||
|
New-TicketComment -Comment "Scheduled disk repair at next boot" -Hidden -DoNotEmail
|
||||||
|
New-TicketComment -Comment "I've made some changes that will require you to restart your computer.`n`nWhenver you're ready, please restart your computer and allow it 30min to complete the startup process."
|
||||||
|
} Else {
|
||||||
|
New-TicketComment -Comment "Disk check was not scheduled to run, as no Windows integrity violations were found." -Hidden -DoNotEmail
|
||||||
|
New-TicketComment -Comment "Your computer has been repaired. Please restart as soon as you're able."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Bill ticket if action was taken
|
||||||
|
If ( $DISMRun -or $ScheduleCheckDisk ) { New-TicketTimerEntry -Comment "Corruption was found and has been repaired." }
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
# Function to update a list of paths
|
||||||
|
Function Update-ListOfPaths {
|
||||||
|
param([System.Collections.ArrayList]$List,[string]$Path)
|
||||||
|
If ( $List -inotcontains $Path ) { $List.Add($Path) | Out-Null }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to expand the UserProfile environment variable as used by MSP360, and update $List
|
||||||
|
Function Add-UserProfilePaths {
|
||||||
|
param([System.Collections.ArrayList]$List,[string]$Path)
|
||||||
|
|
||||||
|
$ChildPath = ($Path -split '%')[2]
|
||||||
|
|
||||||
|
# Get a list of all user profile directories
|
||||||
|
ForEach ( $profile in (Get-ChildItem -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList') ) {
|
||||||
|
$profileImagePath = Get-ItemPropertyValue -Path $([System.Environment]::ExpandEnvironmentVariables($profile.PSPath)) -Name ProfileImagePath
|
||||||
|
|
||||||
|
# Skip profiles located outside C:\Users as MSP360 doesn't currently include these when using the %UserProfile% variable
|
||||||
|
If ( !($profileImagePath -ilike "*${Env:SystemDrive}\Users\*") ) { Continue }
|
||||||
|
|
||||||
|
# Build the path we're trying to add
|
||||||
|
$path = Join-Path -Path $profileImagePath -ChildPath $ChildPath
|
||||||
|
|
||||||
|
# Skip adding the path if it doesn't exist
|
||||||
|
If ( !(Test-Path $path) ) { Continue }
|
||||||
|
|
||||||
|
# Add the path
|
||||||
|
Update-ListOfPaths -List $List -Path $path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Function Use-Path {
|
||||||
|
param([System.Collections.ArrayList]$List,[string]$Path)
|
||||||
|
Switch ( $Path ) {
|
||||||
|
{ $_ -ilike '*%UserProfile%*' } { Add-UserProfilePaths -List $List -Path $Path }
|
||||||
|
{ $_ -ilike '*%*%*' } { Update-ListOfPaths -List $List -Path $([System.Environment]::ExpandEnvironmentVariables($Path)) }
|
||||||
|
default { Update-ListOfPaths -List $List -Path $Path }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Import MSP360 module
|
||||||
|
Import-MSP360Module
|
||||||
|
|
||||||
|
# Get all backup plans
|
||||||
|
$BackupPlans = Get-MBSBackupPlan -OutputType Raw
|
||||||
|
|
||||||
|
# Track the number of processed backup plans so we can put line breaks between them in the output
|
||||||
|
$planIter = 0
|
||||||
|
|
||||||
|
# Iterate over all backup plans
|
||||||
|
ForEach ( $plan in $BackupPlans ) {
|
||||||
|
|
||||||
|
# Add a line break in the output if we've already processed a backup plan
|
||||||
|
If ( $planIter -gt 0 ) { Write-Output "`n" }
|
||||||
|
|
||||||
|
# Function to process each file
|
||||||
|
Function ProcessFile {
|
||||||
|
param([System.IO.FileInfo]$File)
|
||||||
|
|
||||||
|
Switch ( $File ) {
|
||||||
|
|
||||||
|
# Filter out if name matches an excluded path
|
||||||
|
{ $null -ne ($ExcludedPaths | Where-Object { $File.FullName.Replace('\','\\') -match $_.Replace('\','\\') }) } { Update-FilteredFiles $File }
|
||||||
|
|
||||||
|
# Filter out if file size is greater than plan maximum
|
||||||
|
{ $File.Length -gt $MaxFileSize } { Update-FilteredFiles $File }
|
||||||
|
|
||||||
|
# Add item to $IncludeFiles
|
||||||
|
default { $IncludedFiles.Add($item.FullName, $item.Length) | Out-Null }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to add a file to $FilteredFiles
|
||||||
|
Function Update-FilteredFiles {
|
||||||
|
param([System.IO.FileInfo]$File)
|
||||||
|
$FilteredFiles.Add($File.FullName, $File.Length) | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print the plan name that we're currently working on
|
||||||
|
Write-Output ('Plan Name: {0}' -f ($plan.Name))
|
||||||
|
|
||||||
|
# Create array lists to store paths included in the plans
|
||||||
|
[System.Collections.ArrayList]$IncludedPaths = @()
|
||||||
|
[System.Collections.ArrayList]$ExcludedPaths = @()
|
||||||
|
|
||||||
|
# Create array lists to keep files matching certain patterns
|
||||||
|
[System.Collections.Hashtable]$FilteredFiles = @{}
|
||||||
|
[System.Collections.Hashtable]$IncludedFiles = @{}
|
||||||
|
|
||||||
|
# Get all included items from plan
|
||||||
|
If ( $null -ne $plan.Items ) {
|
||||||
|
ForEach ( $obj in $plan.Items ) {
|
||||||
|
ForEach ( $path in $obj.PlanItem.Path ) {
|
||||||
|
Use-Path -List $IncludedPaths -Path $path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get all excluded items from plan
|
||||||
|
If ( $null -ne $plan.ExcludedItems ) {
|
||||||
|
ForEach ( $obj in $plan.ExcludedItems ) {
|
||||||
|
ForEach ( $path in $obj.PlanItem.Path ) {
|
||||||
|
Use-Path -List $ExcludedPaths -Path $path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print all paths in $IncludedPaths
|
||||||
|
If ( $IncludedPaths.Length -gt 0 ) {
|
||||||
|
Write-Output "Paths to backup:"
|
||||||
|
ForEach ( $path in ($IncludedPaths | Sort-Object) ) { Write-Output $path }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Define $MaxFileSize if it exists, or set it to the default value if it can't be obtained
|
||||||
|
If ( $null -ne $plan.MaxFileSize ) { $MaxFileSize = $plan.MaxFileSize } Else { $MaxFileSize = [long]::MaxValue }
|
||||||
|
|
||||||
|
# Process each path in $IncludedPaths
|
||||||
|
ForEach ($path in $IncludedPaths ) {
|
||||||
|
|
||||||
|
# Skip paths that don't exist
|
||||||
|
If ( !(Test-Path $path) ) { Continue }
|
||||||
|
|
||||||
|
# If $path is an individual file, process it separately
|
||||||
|
$item = Get-Item -Path $path -ErrorAction SilentlyContinue
|
||||||
|
If ( $item -is [System.IO.FileInfo] ) { ProcessFile $item ; Continue }
|
||||||
|
|
||||||
|
# Get all children of $path
|
||||||
|
ForEach ( $item in (Get-ChildItem -Path $path -Recurse -Force -ErrorAction SilentlyContinue) ) {
|
||||||
|
|
||||||
|
# Process each file
|
||||||
|
If ( $item -is [System.IO.FileInfo] ) { ProcessFile $item }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print total files and total size
|
||||||
|
Write-Output ('Total Files: {0}' -f ($IncludedFiles.Count))
|
||||||
|
If ( $IncludedFiles.Count -gt 0 ) {
|
||||||
|
$IncludedFilesSize = 0
|
||||||
|
ForEach ($len in $IncludedFiles.Values) { $IncludedFilesSize += $len}
|
||||||
|
Write-Output $('Total size: {0}GB' -f [math]::Round(($IncludedFilesSize/1GB),2) )
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print total excluded files and, optionally, the total size of excluded files as well as their full path
|
||||||
|
Write-Output ('Total Excluded Files: {0}' -f ($FilteredFiles.Count))
|
||||||
|
If ( $FilteredFiles.Count -gt 0 ) {
|
||||||
|
$FilteredFilesSize = 0
|
||||||
|
ForEach ($len in $FilteredFiles.Values) { $FilteredFilesSize += $len}
|
||||||
|
Write-Output $('Total size: {0}GB' -f [math]::Round(($FilteredFilesSize/1GB),2) )
|
||||||
|
Write-Output "All files excluded from plan:"
|
||||||
|
ForEach ($file in $FilteredFiles.GetEnumerator()) {
|
||||||
|
Write-Output $file.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$planIter++
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
#Requires -Version 5.0
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Downloads and runs the DSA manifest collector, with optional upload and cleanup.
|
||||||
|
.DESCRIPTION
|
||||||
|
Tools.ps1 must be dot-sourced by the caller first. Set $DSAManifestSource
|
||||||
|
in the caller's scope before using Run-Script -LivePSScript Get-DSAManifest.
|
||||||
|
The output path is generated in $Global:OutputDirectory with the filename
|
||||||
|
dsa-manifest-HOSTNAME-yyyyMMddHHmmss.csv, using the computer name and current local date and time.
|
||||||
|
$Zip enables ZIP compression; $Upload sends the result to the FileDrop;
|
||||||
|
$DeleteAfterUpload removes the local result only after a confirmed upload.
|
||||||
|
All three accept 'true', '$true', 'yes', 'y', or '1' to enable, and
|
||||||
|
'false', '$false', 'no', 'n', or '0' to disable. Empty values disable the option.
|
||||||
|
Values are case-insensitive and surrounding whitespace is ignored. When
|
||||||
|
$Upload is false, the local CSV or ZIP is retained regardless of $DeleteAfterUpload.
|
||||||
|
#>
|
||||||
|
|
||||||
|
If ( [string]::IsNullOrWhiteSpace([string]$DSAManifestSource) ) {
|
||||||
|
Throw 'Set $DSAManifestSource in the caller before running Get-DSAManifest.'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Normalize RMM text values without treating every nonempty string as true.
|
||||||
|
$DsaOptions = @{
|
||||||
|
Zip = ConvertTo-RmmBoolean -Value $Zip -VariableName 'Zip'
|
||||||
|
Upload = ConvertTo-RmmBoolean -Value $Upload -VariableName 'Upload'
|
||||||
|
DeleteAfterUpload = ConvertTo-RmmBoolean -Value $DeleteAfterUpload -VariableName 'DeleteAfterUpload'
|
||||||
|
}
|
||||||
|
$DsaZipEnabled = $DsaOptions.Zip
|
||||||
|
|
||||||
|
$DSAManifestOutput = Join-Path -Path $Global:OutputDirectory -ChildPath (
|
||||||
|
'dsa-manifest-{0}-{1}.csv' -f $Env:COMPUTERNAME, (Get-Date -Format 'yyyyMMddHHmmss')
|
||||||
|
)
|
||||||
|
New-Item -Path $Global:OutputDirectory -ItemType Directory -Force -ErrorAction Stop | Out-Null
|
||||||
|
|
||||||
|
# Select the executable for the OS, even when the RMM uses 32-bit PowerShell.
|
||||||
|
If ( [Environment]::Is64BitOperatingSystem ) {
|
||||||
|
$DsaDownloadUrl = 'https://xfer.emberkom.com/shares/tools/folders/9b9988fb-c53a-4833-a4ad-68d01a3021bb/files/dsa-collect-windows-amd64-exe/download/dsa-collect-windows-amd64.exe'
|
||||||
|
}
|
||||||
|
Else {
|
||||||
|
$DsaDownloadUrl = 'https://xfer.emberkom.com/shares/tools/folders/9b9988fb-c53a-4833-a4ad-68d01a3021bb/files/dsa-collect-windows-386-exe/download/dsa-collect-windows-386.exe'
|
||||||
|
}
|
||||||
|
|
||||||
|
$DsaExecutablePath = Join-Path -Path $Global:ToolsDirectory -ChildPath 'dsa-collect.exe'
|
||||||
|
New-Item -Path $Global:ToolsDirectory -ItemType Directory -Force -ErrorAction Stop | Out-Null
|
||||||
|
|
||||||
|
Write-Output "Downloading dsa-collect.exe to ${DsaExecutablePath}"
|
||||||
|
$DsaDownloadedFile = Download-FileDirectly -URL $DsaDownloadUrl -File $DsaExecutablePath -ErrorAction Stop
|
||||||
|
|
||||||
|
# Require a returned path and a nonempty download before starting the collector.
|
||||||
|
If ( [string]::IsNullOrWhiteSpace([string]$DsaDownloadedFile) -or
|
||||||
|
!(Test-Path -LiteralPath $DsaExecutablePath -PathType Leaf) ) {
|
||||||
|
Throw "Failed to download dsa-collect.exe to ${DsaExecutablePath}"
|
||||||
|
}
|
||||||
|
If ( (Get-Item -LiteralPath $DsaExecutablePath -ErrorAction Stop).Length -eq 0 ) {
|
||||||
|
Throw "The downloaded dsa-collect.exe is 0 bytes: ${DsaExecutablePath}. The collector was not started. Check the download response and client TLS/BITS settings."
|
||||||
|
}
|
||||||
|
|
||||||
|
$DsaArguments = @('--source', [string]$DSAManifestSource, '--output', [string]$DSAManifestOutput)
|
||||||
|
If ( $DsaZipEnabled ) {
|
||||||
|
$DsaArguments += '--zip'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Quote native arguments explicitly, including paths ending in a backslash.
|
||||||
|
# Start-Process joins its argument list without adding the required quoting.
|
||||||
|
$DsaCommandLine = ($DsaArguments | ForEach-Object {
|
||||||
|
'"{0}"' -f ($_ -replace '(\\*)"', '$1$1\"' -replace '(\\+)$', '$1$1')
|
||||||
|
}) -join ' '
|
||||||
|
|
||||||
|
$DsaCollectionTime = Get-Date
|
||||||
|
Write-Output "Running dsa-collect.exe (ZIP compression: ${DsaZipEnabled})"
|
||||||
|
$DsaProcess = Start-Process -FilePath $DsaExecutablePath -ArgumentList $DsaCommandLine `
|
||||||
|
-Wait -PassThru -WindowStyle Hidden -ErrorAction Stop
|
||||||
|
If ( $DsaProcess.ExitCode -ne 0 ) {
|
||||||
|
Throw "dsa-collect.exe failed with exit code $($DsaProcess.ExitCode)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$DsaResultPath = $DSAManifestOutput
|
||||||
|
If ( $DsaZipEnabled ) {
|
||||||
|
$DsaResultPath = [System.IO.Path]::ChangeExtension($DSAManifestOutput, '.zip')
|
||||||
|
}
|
||||||
|
If ( !(Test-Path -LiteralPath $DsaResultPath -PathType Leaf) ) {
|
||||||
|
Throw "dsa-collect.exe did not create the expected output: ${DsaResultPath}"
|
||||||
|
}
|
||||||
|
Write-Output "DSA manifest created: ${DsaResultPath}"
|
||||||
|
|
||||||
|
If ( $DsaOptions.Upload ) {
|
||||||
|
Write-Output "Uploading DSA manifest: ${DsaResultPath}"
|
||||||
|
$DsaUploadReceipt = Upload-File -Path $DsaResultPath `
|
||||||
|
-Subject "DSA manifest file uploaded from ${Env:COMPUTERNAME}" `
|
||||||
|
-Message "DSA collection performed on ${DSAManifestSource} at $($DsaCollectionTime.ToString('yyyy-MM-dd HH:mm:ss zzz'))" `
|
||||||
|
-ErrorAction Stop
|
||||||
|
If ( [string]::IsNullOrWhiteSpace([string]$DsaUploadReceipt.AttachmentId) -or
|
||||||
|
[string]::IsNullOrWhiteSpace([string]$DsaUploadReceipt.Status) ) {
|
||||||
|
Throw 'The upload did not return a confirmation receipt. The local manifest has been retained.'
|
||||||
|
}
|
||||||
|
Write-Output "DSA manifest uploaded: ${DsaResultPath}"
|
||||||
|
|
||||||
|
If ( $DsaOptions.DeleteAfterUpload ) {
|
||||||
|
Remove-Item -LiteralPath $DsaResultPath -Force -ErrorAction Stop
|
||||||
|
Write-Output "Deleted uploaded DSA manifest: ${DsaResultPath}"
|
||||||
|
}
|
||||||
|
$DsaUploadReceipt
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,528 @@
|
|||||||
|
# Based on the script by lwhitelock, which is based on the original script by Kelvin Tegelaar https://github.com/KelvinTegelaar/AutomaticDocumentation
|
||||||
|
#####################################################################
|
||||||
|
#
|
||||||
|
# Active Directory Details to Hudu
|
||||||
|
#
|
||||||
|
# $HuduAPIKey must be supplied by RMM script
|
||||||
|
|
||||||
|
# $HuduBaseDomain is the base domain of your Hudu instance (without a trailing /)
|
||||||
|
# and must be supplied by RMM script
|
||||||
|
|
||||||
|
# $CompanyName must exactly match the name of the company in Hudu
|
||||||
|
# and must be supplied by RMM script
|
||||||
|
|
||||||
|
# This is the name of the Hudu Asset Layout you want to use.
|
||||||
|
$HuduAssetLayoutName = "Active Directory"
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
# Get the Hudu API Module if not installed
|
||||||
|
if (Get-Module -ListAvailable -Name HuduAPI) {
|
||||||
|
Import-Module HuduAPI
|
||||||
|
} else {
|
||||||
|
Install-Module HuduAPI -Force
|
||||||
|
Import-Module HuduAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set Hudu logon information
|
||||||
|
New-HuduAPIKey $HuduAPIKey
|
||||||
|
New-HuduBaseUrl $HuduBaseDomain
|
||||||
|
|
||||||
|
Function Get-RegistryValue
|
||||||
|
{
|
||||||
|
# Gets the specified registry value or $Null if it is missing
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param
|
||||||
|
(
|
||||||
|
[String] $path,
|
||||||
|
[String] $name,
|
||||||
|
[String] $ComputerName
|
||||||
|
)
|
||||||
|
|
||||||
|
If($ComputerName -eq $env:computername -or $ComputerName -eq "LocalHost")
|
||||||
|
{
|
||||||
|
$key = Get-Item -LiteralPath $path -EA 0
|
||||||
|
If($key)
|
||||||
|
{
|
||||||
|
Return $key.GetValue($name, $Null)
|
||||||
|
}
|
||||||
|
Else
|
||||||
|
{
|
||||||
|
Return $Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Path needed here is different for remote registry access
|
||||||
|
$path1 = $path.SubString( 6 )
|
||||||
|
$path2 = $path1.Replace( '\', '\\' )
|
||||||
|
|
||||||
|
$registry = $null
|
||||||
|
try
|
||||||
|
{
|
||||||
|
## Use the Remote Registry service
|
||||||
|
$registry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey(
|
||||||
|
[Microsoft.Win32.RegistryHive]::LocalMachine,
|
||||||
|
$ComputerName )
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
#$e = $error[ 0 ]
|
||||||
|
#3.06, remove the verbose message as it confised some people
|
||||||
|
#wv "Could not open registry on computer $ComputerName ($e)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$val = $null
|
||||||
|
If( $registry )
|
||||||
|
{
|
||||||
|
$key = $registry.OpenSubKey( $path2 )
|
||||||
|
If( $key )
|
||||||
|
{
|
||||||
|
$val = $key.GetValue( $name )
|
||||||
|
$key.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
$registry.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
Return $val
|
||||||
|
}
|
||||||
|
|
||||||
|
Function GetBasicDCInfo {
|
||||||
|
Param
|
||||||
|
(
|
||||||
|
[Parameter( Mandatory = $true )]
|
||||||
|
[String] $dn # distinguishedName of a DC
|
||||||
|
)
|
||||||
|
|
||||||
|
$DCName = $dn.SubString( 0, $dn.IndexOf( '.' ) )
|
||||||
|
$SrvName = $dn.SubString( $dn.IndexOf( '.' ) + 1 )
|
||||||
|
|
||||||
|
$Results = Get-ADDomainController -Identity $DCName -Server $SrvName -EA 0
|
||||||
|
|
||||||
|
If($? -and $Null -ne $Results)
|
||||||
|
{
|
||||||
|
$GC = $Results.IsGlobalCatalog.ToString()
|
||||||
|
$ReadOnly = $Results.IsReadOnly.ToString()
|
||||||
|
$IPv4Address = $Results.IPv4Address -join ", "
|
||||||
|
$IPv6Address = $Results.IPv6Address -join ", "
|
||||||
|
$ServerOS = $Results.OperatingSystem
|
||||||
|
$tmp = Get-RegistryValue "HKLM:\software\microsoft\windows nt\currentversion" "installationtype" $DCName
|
||||||
|
If( $null -eq $tmp ) { $ServerCore = 'Unknown' }
|
||||||
|
ElseIf( $tmp -eq 'Server Core') { $ServerCore = 'Yes' }
|
||||||
|
Else { $ServerCore = 'No' }
|
||||||
|
}
|
||||||
|
Else
|
||||||
|
{
|
||||||
|
$GC = 'Unable to retrieve status'
|
||||||
|
$ReadOnly = $GC
|
||||||
|
$ServerOS = $GC
|
||||||
|
$ServerCore = $GC
|
||||||
|
$IPv4Address = $GC
|
||||||
|
$IPv6Address = $GC
|
||||||
|
}
|
||||||
|
|
||||||
|
$obj = [PSCustomObject] @{
|
||||||
|
DCName = $DCName
|
||||||
|
GC = $GC
|
||||||
|
ReadOnly = $ReadOnly
|
||||||
|
ServerOS = $ServerOS
|
||||||
|
ServerCore = $ServerCore
|
||||||
|
IPv4Address = $IPv4Address
|
||||||
|
IPv6Address = $IPv6Address
|
||||||
|
}
|
||||||
|
|
||||||
|
Return $obj
|
||||||
|
}
|
||||||
|
|
||||||
|
Function GetTimeServerRegistryKeys {
|
||||||
|
Param
|
||||||
|
(
|
||||||
|
[String] $DCName
|
||||||
|
)
|
||||||
|
|
||||||
|
$AnnounceFlags = Get-RegistryValue "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config" "AnnounceFlags" $DCName
|
||||||
|
If( $null -eq $AnnounceFlags )
|
||||||
|
{
|
||||||
|
## DCName can't be contacted or DCName is an appliance with no registry
|
||||||
|
$AnnounceFlags = 'n/a'
|
||||||
|
$MaxNegPhaseCorrection = 'n/a'
|
||||||
|
$MaxPosPhaseCorrection = 'n/a'
|
||||||
|
$NtpServer = 'n/a'
|
||||||
|
$NtpType = 'n/a'
|
||||||
|
$SpecialPollInterval = 'n/a'
|
||||||
|
$VMICTimeProviderEnabled = 'n/a'
|
||||||
|
$NTPSource = 'Cannot retrieve data from registry'
|
||||||
|
}
|
||||||
|
Else
|
||||||
|
{
|
||||||
|
$MaxNegPhaseCorrection = Get-RegistryValue "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config" "MaxNegPhaseCorrection" $DCName
|
||||||
|
$MaxPosPhaseCorrection = Get-RegistryValue "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config" "MaxPosPhaseCorrection" $DCName
|
||||||
|
$NtpServer = Get-RegistryValue "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Parameters" "NtpServer" $DCName
|
||||||
|
$NtpType = Get-RegistryValue "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Parameters" "Type" $DCName
|
||||||
|
$SpecialPollInterval = Get-RegistryValue "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpClient" "SpecialPollInterval" $DCName
|
||||||
|
$VMICTimeProviderEnabled = Get-RegistryValue "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\VMICTimeProvider" "Enabled" $DCName
|
||||||
|
$NTPSource = Invoke-Command -ComputerName $DCName {w32tm /query /computer:$DCName /source}
|
||||||
|
}
|
||||||
|
|
||||||
|
If( $VMICTimeProviderEnabled -eq 'n/a' )
|
||||||
|
{
|
||||||
|
$VMICEnabled = 'n/a'
|
||||||
|
}
|
||||||
|
ElseIf( $VMICTimeProviderEnabled -eq 0 )
|
||||||
|
{
|
||||||
|
$VMICEnabled = 'Disabled'
|
||||||
|
}
|
||||||
|
Else
|
||||||
|
{
|
||||||
|
$VMICEnabled = 'Enabled'
|
||||||
|
}
|
||||||
|
|
||||||
|
$obj = [PSCustomObject] @{
|
||||||
|
DCName = $DCName.Substring(0, $_.IndexOf( '.'))
|
||||||
|
TimeSource = $NTPSource
|
||||||
|
AnnounceFlags = $AnnounceFlags
|
||||||
|
MaxNegPhaseCorrection = $MaxNegPhaseCorrection
|
||||||
|
MaxPosPhaseCorrection = $MaxPosPhaseCorrection
|
||||||
|
NtpServer = $NtpServer
|
||||||
|
NtpType = $NtpType
|
||||||
|
SpecialPollInterval = $SpecialPollInterval
|
||||||
|
VMICTimeProvider = $VMICEnabled
|
||||||
|
}
|
||||||
|
Return $obj
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-WinADForestInformation {
|
||||||
|
$Data = @{ }
|
||||||
|
$ForestInformation = $(Get-ADForest)
|
||||||
|
$Data.Forest = $ForestInformation
|
||||||
|
$Data.RootDSE = $(Get-ADRootDSE -Properties *)
|
||||||
|
$Data.ForestName = $ForestInformation.Name
|
||||||
|
$Data.ForestNameDN = $Data.RootDSE.defaultNamingContext
|
||||||
|
$Data.Domains = $ForestInformation.Domains
|
||||||
|
$Data.ForestInformation = @{
|
||||||
|
'Forest Name' = $ForestInformation.Name
|
||||||
|
'Root Domain' = $ForestInformation.RootDomain
|
||||||
|
'Forest Functional Level' = $ForestInformation.ForestMode
|
||||||
|
'# of Domains' = ($ForestInformation.Domains).Count
|
||||||
|
'Sites Count' = ($ForestInformation.Sites).Count
|
||||||
|
'Forest Domains' = ($ForestInformation.Domains) -join ", "
|
||||||
|
'Sites' = ($ForestInformation.Sites) -join ", "
|
||||||
|
}
|
||||||
|
|
||||||
|
$Data.UPNSuffixes = Invoke-Command -ScriptBlock {
|
||||||
|
$UPNSuffixList = [PSCustomObject] @{
|
||||||
|
"Primary UPN" = $ForestInformation.RootDomain
|
||||||
|
"UPN Suffixes" = $ForestInformation.UPNSuffixes -join ","
|
||||||
|
}
|
||||||
|
return $UPNSuffixList
|
||||||
|
}
|
||||||
|
|
||||||
|
$Data.GlobalCatalogs = $ForestInformation.GlobalCatalogs
|
||||||
|
$Data.SPNSuffixes = $ForestInformation.SPNSuffixes
|
||||||
|
|
||||||
|
$Data.Sites = Invoke-Command -ScriptBlock {
|
||||||
|
$Sites = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites | Sort-Object
|
||||||
|
$SiteData = foreach ($Site in $Sites) {
|
||||||
|
[PSCustomObject] @{
|
||||||
|
"Site Name" = $site.Name
|
||||||
|
"Subnets" = ($site.Subnets | Sort-Object) -join ", "
|
||||||
|
"Servers" = ($Site.Servers) -join ", "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Return $SiteData
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$Data.FSMO = Invoke-Command -ScriptBlock {
|
||||||
|
[PSCustomObject] @{
|
||||||
|
"Domain" = $ForestInformation.RootDomain
|
||||||
|
"Role" = 'Domain Naming Master'
|
||||||
|
"Holder" = $ForestInformation.DomainNamingMaster
|
||||||
|
}
|
||||||
|
|
||||||
|
[PSCustomObject] @{
|
||||||
|
"Domain" = $ForestInformation.RootDomain
|
||||||
|
"Role" = 'Schema Master'
|
||||||
|
"Holder" = $ForestInformation.SchemaMaster
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($Domain in $ForestInformation.Domains) {
|
||||||
|
$DomainFSMO = Get-ADDomain $Domain | Select-Object PDCEmulator, RIDMaster, InfrastructureMaster
|
||||||
|
|
||||||
|
[PSCustomObject] @{
|
||||||
|
"Domain" = $Domain
|
||||||
|
"Role" = 'PDC Emulator'
|
||||||
|
"Holder" = $DomainFSMO.PDCEmulator
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[PSCustomObject] @{
|
||||||
|
"Domain" = $Domain
|
||||||
|
"Role" = 'Infrastructure Master'
|
||||||
|
"Holder" = $DomainFSMO.InfrastructureMaster
|
||||||
|
}
|
||||||
|
|
||||||
|
[PSCustomObject] @{
|
||||||
|
"Domain" = $Domain
|
||||||
|
"Role" = 'RID Master'
|
||||||
|
"Holder" = $DomainFSMO.RIDMaster
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Return $FSMO
|
||||||
|
}
|
||||||
|
|
||||||
|
$Data.OptionalFeatures = Invoke-Command -ScriptBlock {
|
||||||
|
$OptionalFeatures = $(Get-ADOptionalFeature -Filter * )
|
||||||
|
$Optional = @{
|
||||||
|
'Recycle Bin Enabled' = ''
|
||||||
|
'Privileged Access Management Feature Enabled' = ''
|
||||||
|
}
|
||||||
|
### Fix Optional Features
|
||||||
|
foreach ($Feature in $OptionalFeatures) {
|
||||||
|
if ($Feature.Name -eq 'Recycle Bin Feature') {
|
||||||
|
if ("$($Feature.EnabledScopes)" -eq '') {
|
||||||
|
$Optional.'Recycle Bin Enabled' = $False
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$Optional.'Recycle Bin Enabled' = $True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($Feature.Name -eq 'Privileged Access Management Feature') {
|
||||||
|
if ("$($Feature.EnabledScopes)" -eq '') {
|
||||||
|
$Optional.'Privileged Access Management Feature Enabled' = $False
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$Optional.'Privileged Access Management Feature Enabled' = $True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $Optional
|
||||||
|
### Fix optional features
|
||||||
|
}
|
||||||
|
return $Data
|
||||||
|
}
|
||||||
|
|
||||||
|
$TableHeader = "<table style=`"width: 100%; border-collapse: collapse; border: 1px solid black;`">"
|
||||||
|
$Whitespace = "<br/>"
|
||||||
|
$TableStyling = "<th>", "<th align=`"left`" style=`"background-color:#00adef; border: 1px solid black;`">"
|
||||||
|
|
||||||
|
$RawAD = Get-WinADForestInformation
|
||||||
|
|
||||||
|
$ForestRawInfo = new-object PSCustomObject -property $RawAD.ForestInformation | convertto-html -Fragment | Select-Object -Skip 1
|
||||||
|
$ForestToc = "<div id=`"forest_summary`"></div>"
|
||||||
|
$ForestNice = $ForestToc + $TableHeader + ($ForestRawInfo -replace $TableStyling) + $Whitespace
|
||||||
|
|
||||||
|
$SiteRawInfo = $RawAD.Sites | Select-Object 'Site Name', Servers, Subnets | ConvertTo-Html -Fragment | Select-Object -Skip 1
|
||||||
|
$SiteHeader = "<p id=`"site_summary`"><i>AD Forest Physical Structure.</i></p>"
|
||||||
|
$SiteNice = $SiteHeader + $TableHeader + ($SiteRawInfo -replace $TableStyling) + $Whitespace
|
||||||
|
|
||||||
|
$DomainsRawInfo = $(Get-WinADForestInformation).Domains | ForEach-Object { Get-ADDomain $_ | Select-Object Name, NetBIOSName, DomainMode } | ConvertTo-Html -Fragment | Select-Object -Skip 1
|
||||||
|
$DomainsHeader = "<p id=`"domain_summary`"><i>AD Forest Logical Structure.</i></p>"
|
||||||
|
$DomainsNice = $DomainsHeader + $TableHeader + ($DomainsRawInfo -replace $TableStyling) + $Whitespace
|
||||||
|
|
||||||
|
$OptionalRawFeatures = new-object PSCustomObject -property $RawAD.OptionalFeatures | convertto-html -Fragment | Select-Object -Skip 1
|
||||||
|
$OptionalFeaturesToc = "<div id=`"optional_features`"></div>"
|
||||||
|
$OptionalNice = $OptionalFeaturesToc + $TableHeader + ($OptionalRawFeatures -replace $TableStyling) + $Whitespace
|
||||||
|
|
||||||
|
$UPNRawFeatures = $RawAD.UPNSuffixes | convertto-html -Fragment -as list| Select-Object -Skip 1
|
||||||
|
$UPNToc = "<div id=`"upn_suffixes`"></div>"
|
||||||
|
$UPNNice = $UPNToc + $TableHeader + ($UPNRawFeatures -replace $TableStyling) + $Whitespace
|
||||||
|
|
||||||
|
$DCRawFeatures = $RawAD.GlobalCatalogs| Sort-Object | ForEach-Object { GetBasicDCInfo $_ } | convertto-html -Fragment | Select-Object -Skip 1
|
||||||
|
$DCToc = "<div id=`"domain_controllers`"></div>"
|
||||||
|
$DCNice = $DCToc + $TableHeader + ($DCRawFeatures -replace $TableStyling) + $Whitespace
|
||||||
|
|
||||||
|
$DCRawNTPconfig = $RawAD.GlobalCatalogs | Sort-Object | ForEach-Object { (GetTimeServerRegistryKeys $_) } | convertto-html -Fragment | Select-Object -Skip 1
|
||||||
|
$NTPToc = "<div id=`"ntp_configuration`"></div>"
|
||||||
|
$DCNTPconfigNice = $NTPToc + $TableHeader + ($DCRawNTPconfig -replace $TableStyling) + $Whitespace
|
||||||
|
|
||||||
|
$FSMORawFeatures = $RawAD.FSMO | convertto-html -Fragment | Select-Object -Skip 1
|
||||||
|
$FSMOToc = "<div id=`"fsmo_roles`"></div>"
|
||||||
|
$FSMONice = $FSMOToc + $TableHeader + ($FSMORawFeatures -replace $TableStyling) + $Whitespace
|
||||||
|
|
||||||
|
#$ForestFunctionalLevel = $RawAD.RootDSE.forestFunctionality
|
||||||
|
#$DomainFunctionalLevel = $RawAD.RootDSE.domainFunctionality
|
||||||
|
#$domaincontrollerMaxLevel = $RawAD.RootDSE.domainControllerFunctionality
|
||||||
|
|
||||||
|
$passwordpolicyraw = Get-ADDefaultDomainPasswordPolicy | Select-Object ComplexityEnabled, PasswordHistoryCount, LockoutDuration, LockoutThreshold, MaxPasswordAge, MinPasswordAge | convertto-html -Fragment -As List | Select-Object -skip 1
|
||||||
|
$passwordpolicyheader = "<tr><th>Policy</th><th><b>Setting</b></th></tr>"
|
||||||
|
$passwordToc = "<div id=`"default_password_policies`"></div>"
|
||||||
|
$passwordpolicyNice = $passwordToc + $TableHeader + ($passwordpolicyheader -replace $TableStyling) + ($passwordpolicyraw -replace $TableStyling) + $Whitespace
|
||||||
|
|
||||||
|
$adminsraw = Get-ADGroupMember "Domain Admins" | Select-Object SamAccountName, Name | convertto-html -Fragment | Select-Object -Skip 1
|
||||||
|
$adminsToc = "<div id=`"domain_admins`"></div>"
|
||||||
|
$adminsnice = $adminsToc + $TableHeader + ($adminsraw -replace $TableStyling) + $Whitespace
|
||||||
|
|
||||||
|
$TotalUsers = (Get-AdUser -filter *).count
|
||||||
|
$EnabledUsers = (Get-AdUser -filter * | Where-Object { $_.enabled -eq $true }).count
|
||||||
|
$DisabledUSers = (Get-AdUser -filter * | Where-Object { $_.enabled -eq $false }).count
|
||||||
|
$DomainAdminUsers = (Get-ADGroupMember -Identity "Domain Admins").count
|
||||||
|
$EnterpriseAdminUsers = (Get-ADGroupMember -Identity "Enterprise Admins").count
|
||||||
|
$SchemaAdminUsers = (Get-ADGroupMember -Identity "Schema Admins").count
|
||||||
|
$AdminCountUsers = (Get-ADUser -LDAPFilter "(admincount=1)").count
|
||||||
|
$UsersCountObj = [PSCustomObject] @{
|
||||||
|
'Total' = $TotalUsers
|
||||||
|
'Enabled' = $EnabledUsers
|
||||||
|
'Disabled' = $DisabledUSers
|
||||||
|
'Domain Admins' = $DomainAdminUsers
|
||||||
|
'Enterprise Admins' = $EnterpriseAdminUsers
|
||||||
|
'Schema Admins' = $SchemaAdminUsers
|
||||||
|
'AdminCount users' = $AdminCountUsers
|
||||||
|
}
|
||||||
|
|
||||||
|
$userTotalsRaw = $UsersCountObj | convertto-html -Fragment | Select-Object -Skip 1
|
||||||
|
$userTotalsToc = "<div id=`"user_count`"></div>"
|
||||||
|
$userTotalsNice = $userTotalsToc + $TableHeader + ($userTotalsRaw -replace $TableStyling) + $Whitespace
|
||||||
|
|
||||||
|
$currentDate = Get-Date -Format "dddd dd/MM/yyyy HH:mm K"
|
||||||
|
$toc = '<h2><center>
|
||||||
|
<a href="#forest_summary">FOREST SUMMARY</a> |
|
||||||
|
<a href="#site_summary">SITE SUMMARY</a> |
|
||||||
|
<a href="#domain_summary">DOMAIN SUMMARY</a> |
|
||||||
|
<a href="#domain_controllers">DOMAIN CONTROLLERS</a> |
|
||||||
|
<a href="#ntp_configuration">NTP CONFIGURATION</a> |
|
||||||
|
<a href="#fsmo_roles">FSMO ROLES</a> |
|
||||||
|
<a href="#optional_features">OPTIONAL FEATURES</a> |
|
||||||
|
<a href="#upn_suffixes">UPN SUFFIXES</a> |
|
||||||
|
<a href="#default_password_policies">DEFAULT PASSWORD POLICIES</a> |
|
||||||
|
<a href="#user_count">USER COUNT</a> |
|
||||||
|
<a href="#domain_admins">DOMAIN ADMINS</a>
|
||||||
|
</center></h2><br />'
|
||||||
|
|
||||||
|
# Setup the fields for the Asset
|
||||||
|
$AssetFields = @{
|
||||||
|
|
||||||
|
'last_updated' = $currentDate
|
||||||
|
'table_of_contents' = $toc
|
||||||
|
'forest_name' = $RawAD.ForestName
|
||||||
|
'forest_summary' = $ForestNice
|
||||||
|
'site_summary' = $SiteNice
|
||||||
|
'domain_summary' = $DomainsNice
|
||||||
|
'domain_controllers' = $DCNice
|
||||||
|
'ntp_configuration' = $DCNTPconfigNice
|
||||||
|
'fsmo_roles' = $FSMONice
|
||||||
|
'optional_features' = $OptionalNice
|
||||||
|
'upn_suffixes' = $UPNNice
|
||||||
|
'default_password_policies' = $passwordpolicyNice
|
||||||
|
'domain_admins' = $adminsnice
|
||||||
|
'user_count' = $userTotalsNice
|
||||||
|
}
|
||||||
|
|
||||||
|
# Checking if the FlexibleAsset exists. If not, create a new one.
|
||||||
|
$Layout = Get-HuduAssetLayouts -name $HuduAssetLayoutName
|
||||||
|
|
||||||
|
if (!$Layout) {
|
||||||
|
|
||||||
|
$AssetLayoutFields = @(
|
||||||
|
@{
|
||||||
|
label = 'Last Updated'
|
||||||
|
field_type = 'Text'
|
||||||
|
show_in_list = 'true'
|
||||||
|
position = 1
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
label = 'Table of Contents'
|
||||||
|
field_type = 'RichText'
|
||||||
|
show_in_list = 'false'
|
||||||
|
position = 2
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
label = 'Forest Name'
|
||||||
|
field_type = 'Text'
|
||||||
|
show_in_list = 'true'
|
||||||
|
position = 3
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
label = 'Forest Summary'
|
||||||
|
field_type = 'RichText'
|
||||||
|
show_in_list = 'false'
|
||||||
|
position = 4
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
label = 'Site Summary'
|
||||||
|
field_type = 'RichText'
|
||||||
|
show_in_list = 'false'
|
||||||
|
position = 5
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
label = 'Domain Summary'
|
||||||
|
field_type = 'RichText'
|
||||||
|
show_in_list = 'false'
|
||||||
|
position = 6
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
label = 'Domain Controllers'
|
||||||
|
field_type = 'RichText'
|
||||||
|
show_in_list = 'false'
|
||||||
|
position = 7
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
label = 'NTP Configuration'
|
||||||
|
field_type = 'RichText'
|
||||||
|
show_in_list = 'false'
|
||||||
|
position = 8
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
label = 'FSMO Roles'
|
||||||
|
field_type = 'RichText'
|
||||||
|
show_in_list = 'false'
|
||||||
|
position = 9
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
label = 'Optional Features'
|
||||||
|
field_type = 'RichText'
|
||||||
|
show_in_list = 'false'
|
||||||
|
position = 10
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
label = 'UPN Suffixes'
|
||||||
|
field_type = 'RichText'
|
||||||
|
show_in_list = 'false'
|
||||||
|
position = 11
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
label = 'Default Password Policies'
|
||||||
|
field_type = 'RichText'
|
||||||
|
show_in_list = 'false'
|
||||||
|
position = 12
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
label = 'User Count'
|
||||||
|
field_type = 'RichText'
|
||||||
|
show_in_list = 'false'
|
||||||
|
position = 13
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
label = 'Domain Admins'
|
||||||
|
field_type = 'RichText'
|
||||||
|
show_in_list = 'false'
|
||||||
|
position = 14
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Host "Creating New Asset Layout"
|
||||||
|
#$NewLayout = New-HuduAssetLayout -name $HuduAssetLayoutName -icon "fas fa-sitemap" -color "#00adef" -icon_color "#000000" -include_passwords $false -include_photos $false -include_comments $false -include_files $false -fields $AssetLayoutFields
|
||||||
|
New-HuduAssetLayout -name $HuduAssetLayoutName -icon "fas fa-sitemap" -color "#00adef" -icon_color "#000000" -include_passwords $false -include_photos $false -include_comments $false -include_files $false -fields $AssetLayoutFields
|
||||||
|
$Layout = Get-HuduAssetLayouts -name $HuduAssetLayoutName
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$Company = Get-HuduCompanies -name $CompanyName
|
||||||
|
if ($company) {
|
||||||
|
#Upload data to Hudu
|
||||||
|
$Asset = Get-HuduAssets -name $RawAD.ForestName -companyid $company.id -assetlayoutid $layout.id
|
||||||
|
|
||||||
|
#If the Asset does not exist, we edit the body to be in the form of a new asset, if not, we just upload.
|
||||||
|
if (!$Asset) {
|
||||||
|
Write-Host "Creating new Asset"
|
||||||
|
$Asset = New-HuduAsset -name $RawAD.ForestName -company_id $company.id -asset_layout_id $layout.id -fields $AssetFields
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Host "Updating Asset"
|
||||||
|
$Asset = Set-HuduAsset -asset_id $Asset.id -name $RawAD.ForestName -company_id $company.id -asset_layout_id $layout.id -fields $AssetFields
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Write-Host "$CompanyName was not found in Hudu"
|
||||||
|
}
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
param(
|
|
||||||
[Parameter(Mandatory=$false)][int]$CUSTOMER_ID=1,
|
|
||||||
[Parameter(Mandatory=$false)][string]$INTEGRATOR_ID,
|
|
||||||
[Parameter(Mandatory=$false)][string]$AGENT_FILENAME="AteraAgentSetup.msi",
|
|
||||||
[Parameter(Mandatory=$false)][switch]$FORCE
|
|
||||||
)
|
|
||||||
|
|
||||||
# Make sure $INTEGRATOR_ID exists, otherwise exit this script as an agent cannot be downloaded
|
|
||||||
#If ( ($INTEGRATOR_ID -eq $null) -or ($INTEGRATOR_ID -eq '') ) { Exit }
|
|
||||||
|
|
||||||
# Source the Tools.ps1 script
|
|
||||||
Function SourceTools
|
|
||||||
{ . $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1'))) }
|
|
||||||
. SourceTools
|
|
||||||
|
|
||||||
# Build the path for $AGENT_INSTALLER
|
|
||||||
$AGENT_INSTALLER = '{0}{1}' -f (Get-TempPath), $AGENT_FILENAME
|
|
||||||
|
|
||||||
# Make sure we need to install the agent
|
|
||||||
If ( (Test-Path "${Env:ProgramFiles}\ATERA Networks\AteraAgent\AteraAgent.exe") -or (Test-Path "${Env:ProgramFiles(x86)}\ATERA Networks\AteraAgent\AteraAgent.exe") )
|
|
||||||
{ $INSTALL = $false ; $PreviousInstall = $true ; Write-Output "Atera agent is already installed on this endpoint" } Else { $INSTALL = $true ; $PreviousInstall = $false }
|
|
||||||
|
|
||||||
If ( $FORCE ) { $INSTALL = $true }
|
|
||||||
|
|
||||||
If ( $INSTALL ) {
|
|
||||||
|
|
||||||
# Uninstall if $FORCE is true
|
|
||||||
If ( $PreviousInstall ) {
|
|
||||||
Write-Output "Getting information from existing installation"
|
|
||||||
$ACCOUNT_ID = (Get-ItemPropertyValue -Path 'HKLM:SOFTWARE\ATERA Networks\AlphaAgent' -Name 'AccountId' -ErrorAction SilentlyContinue)
|
|
||||||
$AGENT_ID = (Get-ItemPropertyValue -Path 'HKLM:SOFTWARE\ATERA Networks\AlphaAgent' -Name 'AgentId' -ErrorAction SilentlyContinue)
|
|
||||||
$CUSTOMER_ID = (Get-ItemPropertyValue -Path 'HKLM:SOFTWARE\ATERA Networks\AlphaAgent' -Name 'CompanyId' -ErrorAction SilentlyContinue)
|
|
||||||
$INTEGRATOR_ID = (Get-ItemPropertyValue -Path 'HKLM:SOFTWARE\ATERA Networks\AlphaAgent' -Name 'IntegratorLogin' -ErrorAction SilentlyContinue)
|
|
||||||
Write-Output "AccountID : ${ACCOUNT_ID}"
|
|
||||||
Write-Output "AgentID : ${AGENT_ID}"
|
|
||||||
Write-Output "CustomerID : ${CUSTOMER_ID}"
|
|
||||||
Write-Output "IntegratorID: ${INTEGRATOR_ID}"
|
|
||||||
|
|
||||||
#Download and run the uninstall script
|
|
||||||
$UninstallScript = Download-File -URL 'https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Uninstall-AteraAgent.ps1'
|
|
||||||
Try { Start-Process "powershell.exe" -ArgumentList "-ExecutionPolicy Bypass -Noninteractive -File ""${UninstallScript}""" -Wait }
|
|
||||||
Catch { Write-Error $_.Exception.Message }
|
|
||||||
}
|
|
||||||
|
|
||||||
# Replace the '@' with '%40' - not sure why this is necessary, but it is
|
|
||||||
$INTEGRATOR_ID = $INTEGRATOR_ID.Replace('@','%40')
|
|
||||||
|
|
||||||
# Download agent installer
|
|
||||||
Download-File -URL "https://app.atera.com/GetAgent/Msi/?customerId=${CUSTOMER_ID}&integratorLogin=${INTEGRATOR_ID}" -File "${AGENT_INSTALLER}"
|
|
||||||
|
|
||||||
# Install the agent
|
|
||||||
Write-Output "Installing Atera agent from ""${AGENT_INSTALLER}"""
|
|
||||||
Try { Start-Process "msiexec.exe" -ArgumentList "/i ""${AGENT_INSTALLER}"" /qn /norestart" -Wait }
|
|
||||||
Catch { Write-Error $_.Exception.Message }
|
|
||||||
|
|
||||||
# Delete the agent installer
|
|
||||||
Write-Output "Deleting ""${AGENT_INSTALLER}"""
|
|
||||||
Try { Remove-Item $AGENT_INSTALLER -Force }
|
|
||||||
Catch { Write-Error $_.Exception.Message }
|
|
||||||
|
|
||||||
If ( $PreviousInstall -and $ACCOUNT_ID -and $AGENT_ID -and $CUSTOMER_ID) {
|
|
||||||
Write-Output "Restoring agent information from previous install"
|
|
||||||
Write-Output " - Stopping AteraAgent service"
|
|
||||||
Stop-Service -Name 'AteraAgent' -Force
|
|
||||||
Write-Output " - Restoring AgentID and AccountID from previous install"
|
|
||||||
Set-ItemProperty -Path 'HKLM:SOFTWARE\ATERA Networks\AlphaAgent' -Name 'AccountId' -Value $ACCOUNT_ID
|
|
||||||
Set-ItemProperty -Path 'HKLM:SOFTWARE\ATERA Networks\AlphaAgent' -Name 'AgentId' -Value $AGENT_ID
|
|
||||||
Write-Output " - Starting AteraAgent service"
|
|
||||||
Start-Service -Name 'AteraAgent'
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
# Delete this script
|
|
||||||
Remove-Item $MyInvocation.MyCommand.Path -Force
|
|
||||||
@@ -29,7 +29,7 @@ If ( -not (Test-Path $SHORTCUT_PATH) ) {
|
|||||||
If ( $BROWSER_PATH ) {
|
If ( $BROWSER_PATH ) {
|
||||||
|
|
||||||
Write-Output "Installing Deltek Vision for ${APP_NAME}"
|
Write-Output "Installing Deltek Vision for ${APP_NAME}"
|
||||||
Create-Shortcut -Path "${Env:Public}\Desktop\Deltek Vision.lnk" -TargetPath $BROWSER_PATH -Arguments $VisionURL -Description "Launch Deltek Vision"
|
New-Shortcut -Path "${Env:Public}\Desktop\Deltek Vision.lnk" -TargetPath $BROWSER_PATH -Arguments $VisionURL -Description "Launch Deltek Vision"
|
||||||
}
|
}
|
||||||
Else { Write-Output "A compatible browser could not be found: ${BROWSER_PATH}" }
|
Else { Write-Output "A compatible browser could not be found: ${BROWSER_PATH}" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
# NOTE: The $CONFIG variable must be set by the calling script
|
# NOTE: The $CONFIG variable must be set by the calling script
|
||||||
|
|
||||||
|
# Check whether agent is already installed
|
||||||
|
If ( Test-Path "${Env:ProgramFiles}\ESET\RemoteAdministrator\Agent\ERAAgent.exe" ) {
|
||||||
|
|
||||||
|
# End the script if no errors are found in the status log
|
||||||
|
If ( !(Select-String -Path "${Env:ProgramData}\ESET\RemoteAdministrator\Agent\EraAgentApplicationData\Logs\status.html" -CaseSensitive -Pattern 'Error:' -Quiet) ) {
|
||||||
|
Microsoft.Powershell.Utility\Write-Output "ESET agent is already installed and no errors were found. This script will now exit."
|
||||||
|
Return
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "ESET agent is already installed, but errors were found in the status log. This script will continue and attempt to reinstall the agent."
|
||||||
|
}
|
||||||
|
|
||||||
# Set path to temp directory
|
# Set path to temp directory
|
||||||
$TEMPDIR = '{0}eset-protect-deployment' -f (Get-TempPath)
|
$TEMPDIR = '{0}eset-protect-deployment' -f (Get-TempPath)
|
||||||
|
|
||||||
@@ -13,11 +25,11 @@ If ( Test-Path $TEMPDIR ) {
|
|||||||
# Create $TEMPDIR
|
# Create $TEMPDIR
|
||||||
Write-Output "Creating: ${TEMPDIR}"
|
Write-Output "Creating: ${TEMPDIR}"
|
||||||
Try { New-Item -Path $TEMPDIR -ItemType Directory -Force -ErrorAction Stop }
|
Try { New-Item -Path $TEMPDIR -ItemType Directory -Force -ErrorAction Stop }
|
||||||
Catch { Write-Error $_.Exception.Message ; Exit }
|
Catch { Write-Error $_.Exception.Message ; Return }
|
||||||
|
|
||||||
# Write $CONFIG to install_config.ini
|
# Write $CONFIG to install_config.ini
|
||||||
Try { $CONFIG | WriteToFile_UTF8NoBOM -FilePath "${TEMPDIR}\install_config.ini" }
|
Try { $CONFIG | WriteToFile_UTF8NoBOM -FilePath "${TEMPDIR}\install_config.ini" }
|
||||||
Catch { Write-Output "Cannot write configuration file! This script will exit." ; Write-Error $_.Exception.Message ; Exit }
|
Catch { Write-Output "Cannot write configuration file! This script will exit." ; Write-Error $_.Exception.Message ; Return }
|
||||||
|
|
||||||
# Get bitness of endpoint and set $AGENTNAME
|
# Get bitness of endpoint and set $AGENTNAME
|
||||||
If ( Is64bit ) { $AGENTNAME = "agent_x64.msi" } Else { $AGENTNAME = "agent_x86.msi" }
|
If ( Is64bit ) { $AGENTNAME = "agent_x64.msi" } Else { $AGENTNAME = "agent_x86.msi" }
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
# This script requires the $Edition variable is set from the RMM script
|
# Specify URL to "Backup for Windows" agent installer
|
||||||
If (! $Edition ) { $Edition = 'desktop' }
|
$ECB_WIN_URL = 'https://console.msp360.com/api/build/download?urlParams=OEFsMzhldm1SbjhaSDhBc2VtYkpUREM3Y2pJSzg0REU4dkVtR1pHREkxMGFFUk9qZXJnSXk5SUF0ei8wZlZOWTBzNG1zVDV0ckVMdStZVzBsam5uZklMVnZLeTBMVmxGNWt4VDhSMzNIL3VmK3VHNmxBVEZTQkxhMFhNR2ZuZzJCeE80MDJxVkRSWXdNa0dqSHdpbUN1bE01c3BWNFBVUmt2Q1c4endncjVZemFSSjEyYmFNL3hESHR2eWpVYXpBVlV2eU1kZVZFaEpvalk2Rk40Wk96UG9QdUwxYnZrRHREMmhLeTZoMW5Rbz0*&brandId=fb8308e2-448a-4645-a5f6-a3632a7e57f4'
|
||||||
|
|
||||||
# Specify URL to "Backup for Windows" agent installer
|
|
||||||
$ECB_WIN_URL = 'https://s3.amazonaws.com/cb_setups/MBS/53CFB286-7A97-4FDF-8CFA-475C0DB63A9A/Brands/FB8308E2-448A-4645-A5F6-A3632A7E57F4/EmberkomCloudBackup_v7.8.7.58_ALLEDITIONS_Setup.exe'
|
|
||||||
|
|
||||||
# Specify URL to "Backup Virtual Machine Edition" agent installer
|
# Specify URL to "Backup Virtual Machine Edition" agent installer
|
||||||
$ECB_VMM_URL = 'https://s3.amazonaws.com/cb_setups/MBS/53CFB286-7A97-4FDF-8CFA-475C0DB63A9A/Brands/FB8308E2-448A-4645-A5F6-A3632A7E57F4/EmberkomCloudBackup_v7.8.7.58_VMWARE_Setup.exe'
|
$ECB_VMM_URL = 'https://console.msp360.com/api/build/download?urlParams=OEFsMzhldm1SbjhaSDhBc2VtYkpUREM3Y2pJSzg0REU4dkVtR1pHREkxMGFFUk9qZXJnSXk5SUF0ei8wZlZOWUpYM2xDY3ErMkpOVGpuWTRISDNFcFRFdWRibDgvTm8vUDhpWndON3kzaHNwOTFVa3h6WUdoeVVod1JmT2lRQ1FlVTE3Y0pnUncwSEJwS3FFcDBmTWt3cEdnSy9YemRsSTRiQWJHNi96cUtWbzZsc25QVllaR1QyQ21VVS95dFFVTkNndXdPVStGQXBIT0FZb2FIUzFuZy9mMU5qZUd5emhab1hFYjZWODB4ND0*&brandId=fb8308e2-448a-4645-a5f6-a3632a7e57f4'
|
||||||
|
|
||||||
# Set the correct download URL and agent edition
|
# Set the correct download URL and agent edition
|
||||||
switch ( $Edition.ToLower() ) {
|
switch ( $Edition.ToLower() ) {
|
||||||
@@ -16,11 +13,38 @@ switch ( $Edition.ToLower() ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Install and import additional Powershell module
|
# Install and import additional Powershell module
|
||||||
Install-MSP360Module
|
Import-MSP360Module
|
||||||
Import-Module -Name MSP360
|
|
||||||
|
|
||||||
# Download the installer
|
# Exit this script if the agent is already installed
|
||||||
$Installer = Download-File -URL $URL
|
If ( $(Get-MBSAgent -ErrorAction SilentlyContinue) ) {
|
||||||
|
Microsoft.Powershell.Utility\Write-Output "Backup agent is already installed. This script will now exit."
|
||||||
|
Return
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# The URL is now too long to pass as a variable to other functions and cmdlets, so this is a workaround to download the file
|
||||||
|
#
|
||||||
|
|
||||||
|
# Make sure the URL is valid
|
||||||
|
$StatusCode = [System.Net.WebRequest]::Create($URL).GetResponse().StatusCode
|
||||||
|
If ( !(($StatusCode -ge 200) -and ($StatusCode -lt 400)) ) { Write-Output "URL cannot be reached (status code ${StatusCode}): ${URL}" ; Return }
|
||||||
|
|
||||||
|
# Make a local file to download to
|
||||||
|
$Installer = '{0}EmberkomCloudBackup.exe' -f (Get-TempPath)
|
||||||
|
|
||||||
|
# Delete previous local file if it exists
|
||||||
|
If ( Test-Path $Installer ) { Write-Output "Deleting previously downloaded file: ${Installer}" ; Remove-Item $Installer -Force -ErrorAction SilentlyContinue }
|
||||||
|
|
||||||
|
# Download the file
|
||||||
|
Try { (New-Object System.Net.WebClient).DownloadFile($URL,$Installer) }
|
||||||
|
Catch { Write-Error $_.Exception.Message ; Remove-Item $Installer -Force -ErrorAction SilentlyContinue }
|
||||||
|
|
||||||
|
# Make sure the file exists
|
||||||
|
If ( !(Test-Path $Installer) ) { Write-Output "There was a problem downloading the file, this script will not continue" ; Return }
|
||||||
|
|
||||||
|
#
|
||||||
|
# End the workaround
|
||||||
|
#
|
||||||
|
|
||||||
# Install the agent
|
# Install the agent
|
||||||
Write-Output "Installing Emberkom Cloud Backup agent"
|
Write-Output "Installing Emberkom Cloud Backup agent"
|
||||||
@@ -28,19 +52,27 @@ Try { Start-Process "${Installer}" -ArgumentList "/S" -Wait }
|
|||||||
Catch { Write-Error $_.Exception.Message }
|
Catch { Write-Error $_.Exception.Message }
|
||||||
|
|
||||||
# Add backup user to installed product
|
# Add backup user to installed product
|
||||||
Try {
|
Write-Output "Adding backup user account: ${MSP360Username}"
|
||||||
$MSP360Password = ConvertTo-SecureString -string $MSP360Password -AsPlainText -Force
|
Try { Add-MBSUserAccount -User $MSP360Username -Password $(ConvertTo-SecureString -string $MSP360Password -AsPlainText -Force) }
|
||||||
Write-Output "Adding backup user account: ${MSP360Username}"
|
|
||||||
Add-MBSUserAccount -User $MSP360Username -Password $MSP360Password
|
|
||||||
}
|
|
||||||
Catch { Write-Error $_.Exception.Message }
|
Catch { Write-Error $_.Exception.Message }
|
||||||
|
|
||||||
|
# Wait a few seconds to let the agent check-in
|
||||||
|
Start-Sleep -Seconds 10
|
||||||
|
|
||||||
# Set the agent edition
|
# Set the agent edition
|
||||||
# Note: the product edition must be set *after* the user is added
|
# Note: the product edition must be set *after* the user is added
|
||||||
Write-Output "Setting Emberkom Cloud Backup agent edition to ""$AgentEdition"""
|
Write-Output "Setting Emberkom Cloud Backup agent edition to ""$AgentEdition"""
|
||||||
Try { Set-MBSAgentSetting -Edition $AgentEdition -Verbose }
|
Try {
|
||||||
|
Switch ([string]::IsNullOrEmpty($MasterPassword)) {
|
||||||
|
$true { Set-MBSAgentSetting -Edition $AgentEdition -Verbose }
|
||||||
|
$false { Set-MBSAgentSetting -Edition $AgentEdition -MasterPassword $(ConvertTo-SecureString -String $MasterPassword -AsPlainText -Force) -Verbose }
|
||||||
|
}
|
||||||
|
}
|
||||||
Catch { Write-Error $_.Exception.Message }
|
Catch { Write-Error $_.Exception.Message }
|
||||||
|
|
||||||
|
# Delete $Installer
|
||||||
|
If ( Test-Path $Installer ) { Write-Output "Deleting downloaded file: ${Installer}" ; Remove-Item $Installer -Force -ErrorAction SilentlyContinue }
|
||||||
|
|
||||||
# Delete the desktop icon
|
# Delete the desktop icon
|
||||||
"${Env:PUBLIC}\Desktop\Emberkom Backup.LNK",
|
If ( Test-Path "${Env:PUBLIC}\Desktop\Emberkom Backup.LNK" ) { Remove-Item -Path "${Env:PUBLIC}\Desktop\Emberkom Backup.LNK" -Force -ErrorAction SilentlyContinue }
|
||||||
"${Env:PUBLIC}\Desktop\Emberkom Cloud Backup.LNK" | Remove-File
|
If ( Test-Path "${Env:PUBLIC}\Desktop\Emberkom Cloud Backup.LNK" ) { Remove-Item -Path "${Env:PUBLIC}\Desktop\Emberkom Cloud Backup.LNK" -Force -ErrorAction SilentlyContinue }
|
||||||
|
|||||||
+54
-11
@@ -1,15 +1,58 @@
|
|||||||
## Path to the MSI installer
|
# Exit early if apps are running
|
||||||
$MSIURL = 'https://enscape-installer.chaosgroup.com/installer.enscape.io/Enscape-3.5.2%2B112393.msi'
|
$ExitEarly = $false
|
||||||
|
$AppNames = @()
|
||||||
|
If ( IsRunning -Name 'revit' ) { $ExitEarly = $true ; $AppNames.Add('Revit') }
|
||||||
|
If ( IsRunning -Name 'sketchup' ) { $ExitEarly = $true ; $AppNames.Add('SketchUp') }
|
||||||
|
If ( IsRunning -Name 'archicad' ) { $ExitEarly = $true ; $AppNames.Add('ArchiCAD') }
|
||||||
|
If ( IsRunning -Name 'autocad' ) { $ExitEarly = $true ; $AppNames.Add('AutoCAD') }
|
||||||
|
If ( IsRunning -Name 'rhino' ) { $ExitEarly = $true ; $AppNames.Add('Rhino') }
|
||||||
|
If ( IsRunning -Name 'vectorworks' ) { $ExitEarly = $true ; $AppNames.Add('VectorWorks') }
|
||||||
|
If ( $ExitEarly ) { Write-Output "Installation cannot continue because the following apps are running: ${AppNames}." ; Return }
|
||||||
|
|
||||||
## Run the installer
|
# Path to the MSI installer
|
||||||
$INSTALLER = Download-File -URL $MSIURL
|
$URL = 'https://enscape-installer.chaosgroup.com/installer.enscape.io/Enscape-4.0.2.11.exe'
|
||||||
Write-Output "Installing Enscape from ""${INSTALLER}"""
|
|
||||||
Try { Start-Process "msiexec.exe" -ArgumentList "/i ${INSTALLER} /qn /norestart ACCEPTEULA=1 ALLUSERS=1" -Wait -ErrorAction Stop }
|
|
||||||
Catch { Write-Error $_.Exception.Message }
|
|
||||||
|
|
||||||
## Delete installer
|
# Set path to temp directory
|
||||||
Try {
|
$TEMPDIR = '{0}enscape-installer' -f (Get-TempPath)
|
||||||
Write-Output "Deleting downloaded installation package"
|
|
||||||
Remove-Item $INSTALLER -Force -ErrorAction SilentlyContinue
|
# Recursively delete $TEMPDIR if it already exists
|
||||||
|
If ( Test-Path $TEMPDIR ) {
|
||||||
|
Write-Output """${TEMPDIR}"" already exists and will be deleted"
|
||||||
|
Try { Remove-Item -Path $TEMPDIR -Recurse -Force -ErrorAction SilentlyContinue }
|
||||||
|
Catch { Write-Error $_.Exception.Message }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Create $TEMPDIR
|
||||||
|
Write-Output "Creating: ${TEMPDIR}"
|
||||||
|
Try { New-Item -Path $TEMPDIR -ItemType Directory -Force -ErrorAction Stop }
|
||||||
|
Catch { Write-Error $_.Exception.Message ; Return }
|
||||||
|
|
||||||
|
# Write $CONFIG to config.xml
|
||||||
|
$CONFIG = @'
|
||||||
|
<DefValues>
|
||||||
|
<Value Name="INSTALL_SKETCHUP" DataType="value">1</Value>
|
||||||
|
<Value Name="STDROOT" DataType="value">C:\Program Files\Enscape</Value>
|
||||||
|
<Value Name="INSTALL_RHINO" DataType="value">1</Value>
|
||||||
|
<Value Name="INSTALL_ARCHICAD" DataType="value">1</Value>
|
||||||
|
<Value Name="LOCALE" DataType="value">en-US</Value>
|
||||||
|
<Value Name="INSTALL_REVIT" DataType="value">1</Value>
|
||||||
|
<Value Name="INSTALL_VECTORWORKS" DataType="value">1</Value>
|
||||||
|
</DefValues>
|
||||||
|
|
||||||
|
'@
|
||||||
|
Try { $CONFIG | WriteToFile_UTF8NoBOM -FilePath "${TEMPDIR}\config.xml" }
|
||||||
|
Catch { Write-Output "Cannot write configuration file! This script will exit." ; Write-Error $_.Exception.Message ; Return }
|
||||||
|
|
||||||
|
# Download the installer
|
||||||
|
$Installer = "${TEMPDIR}\$(Split-Path $URL -Leaf)"
|
||||||
|
Download-File -URL $URL -File $Installer
|
||||||
|
|
||||||
|
# Install package
|
||||||
|
If ( !(Test-Path $Installer) ) { Write-Output "Cannot download installer! This script will exit." ; Return }
|
||||||
|
Write-Output "Installing Enscape from ""${Installer}"""
|
||||||
|
Start-Process $Installer -ArgumentList "-gui=0 -acceptEULA -configFile=""${TEMPDIR}\config.xml"" -quiet=1 -ignoreErrors=1" -Wait
|
||||||
|
|
||||||
|
# Delete the installer
|
||||||
|
Write-Output "Deleting temp directory and its contents"
|
||||||
|
Try { Remove-Item -Path $TEMPDIR -Recurse -Force -ErrorAction SilentlyContinue }
|
||||||
Catch { Write-Error $_.Exception.Message }
|
Catch { Write-Error $_.Exception.Message }
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Path to the MSI installer
|
# Path to the MSI installer
|
||||||
$MSIURL = 'https://lfupdate.s3.amazonaws.com/clients/outlook/admin_msi/LiquidFiles_Admin_2.1.10.msi'
|
$MSIURL = 'https://lfupdate.s3.amazonaws.com/clients/outlook/admin_msi/LiquidFiles_OutlookPlugin_Admin_3.0.37.msi'
|
||||||
|
|
||||||
# Set installer args
|
# Set installer args
|
||||||
If (-not [string]::IsNullOrEmpty($LFHost) ) {
|
If (-not [string]::IsNullOrEmpty($LFHost) ) {
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
param(
|
|
||||||
[Parameter(Mandatory=$true)][string]$TOKEN,
|
|
||||||
[Parameter(Mandatory=$true)][int]$CUSTOMER_ID,
|
|
||||||
[Parameter(Mandatory=$false)][int]$SOFTWARE_ID=101,
|
|
||||||
[Parameter(Mandatory=$false)][string]$SERVER='manage.emberkom.com',
|
|
||||||
[Parameter(Mandatory=$false)][string]$AGENT_FILENAME='EmberkomAgentSetup.exe'
|
|
||||||
)
|
|
||||||
|
|
||||||
## Source the Tools.ps1 script
|
|
||||||
Function SourceTools
|
|
||||||
{ . $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1'))) }
|
|
||||||
. SourceTools
|
|
||||||
|
|
||||||
## Build the path for $AGENT_INSTALLER
|
|
||||||
$AGENT_INSTALLER = '{0}{1}' -f (Get-TempPath), $AGENT_FILENAME
|
|
||||||
|
|
||||||
## Get the right path to agent.exe based on the system bit-level
|
|
||||||
If ( Is64bit ) { $AGENT_EXE = "${Env:ProgramFiles(x86)}\N-able Technologies\Windows Agent\bin\agent.exe" }
|
|
||||||
Else { $AGENT_EXE = "${Env:ProgramFiles}\N-able Technologies\Windows Agent\bin\agent.exe" }
|
|
||||||
|
|
||||||
## Make sure we need to install the agent
|
|
||||||
If ( !(Test-Path $AGENT_EXE) )
|
|
||||||
{
|
|
||||||
## Download agent and get path
|
|
||||||
$AGENT_INSTALLER = Download-File -URL 'https://manage.emberkom.com/download/2020.1.5.425/winnt/N-central/WindowsAgentSetup.exe' -File $AGENT_INSTALLER
|
|
||||||
|
|
||||||
## Install the agent
|
|
||||||
Install-Program $AGENT_INSTALLER -Arguments "/s /v"" /qn CUSTOMERID=${CUSTOMER_ID} CUSTOMERSPECIFIC=1 REGISTRATION_TOKEN=${TOKEN} SERVERPROTOCOL=HTTPS SERVERADDRESS=${SERVER} SERVERPORT=443""" -Delete
|
|
||||||
|
|
||||||
} Else { Write-Output "N-Central agent is already installed on this endpoint." }
|
|
||||||
|
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# $COMPANY must be supplied by RMM
|
||||||
|
$PRODUCT = 'O365ProPlusRetail'
|
||||||
|
$BITNESS = '64'
|
||||||
|
$MIGARCH = 'TRUE'
|
||||||
|
$CHANNEL = 'SemiAnnual'
|
||||||
|
$UPDATES = 'TRUE'
|
||||||
|
$DISPLAY = 'None' # "None", "Full"
|
||||||
|
$ACCEULA = 'TRUE'
|
||||||
|
|
||||||
|
# Make changes if the system is 32bit
|
||||||
|
If ( !(Is64Bit) ) { $BITNESS = '32'; $MIGARCH = 'FALSE' }
|
||||||
|
|
||||||
|
$CONFIG = @"
|
||||||
|
<Configuration ID="e59fdca7-843c-4bee-8edf-33b4f8fb750f">
|
||||||
|
<Add OfficeClientEdition="${BITNESS}" Channel="${CHANNEL}" MigrateArch="${MIGARCH}">
|
||||||
|
<Product ID="${PRODUCT}">
|
||||||
|
<Language ID="en-us" />
|
||||||
|
<ExcludeApp ID="Groove" />
|
||||||
|
<ExcludeApp ID="Lync" />
|
||||||
|
</Product>
|
||||||
|
</Add>
|
||||||
|
<Property Name="SharedComputerLicensing" Value="0" />
|
||||||
|
<Property Name="FORCEAPPSHUTDOWN" Value="TRUE" />
|
||||||
|
<Property Name="DeviceBasedLicensing" Value="0" />
|
||||||
|
<Property Name="SCLCacheOverride" Value="0" />
|
||||||
|
<Updates Enabled="${UPDATES}" Channel="${CHANNEL}" />
|
||||||
|
<RemoveMSI />
|
||||||
|
<Display Level="${DISPLAY}" AcceptEULA="${ACCEULA}" />
|
||||||
|
<AppSettings>
|
||||||
|
<Setup Name="Company" Value="${COMPANY}" />
|
||||||
|
</AppSettings>
|
||||||
|
</Configuration>
|
||||||
|
"@
|
||||||
|
|
||||||
|
# Set path to temp directory
|
||||||
|
$TEMPDIR = '{0}microsoft-office-deployment' -f (Get-TempPath)
|
||||||
|
|
||||||
|
# Recursively delete $TEMPDIR if it already exists
|
||||||
|
If ( Test-Path $TEMPDIR ) {
|
||||||
|
Write-Output """${TEMPDIR}"" already exists and will be deleted"
|
||||||
|
Try { Remove-Item -Path $TEMPDIR -Recurse -Force -ErrorAction SilentlyContinue }
|
||||||
|
Catch { Write-Error $_.Exception.Message }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create $TEMPDIR
|
||||||
|
Write-Output "Creating: ${TEMPDIR}"
|
||||||
|
Try { New-Item -Path $TEMPDIR -ItemType Directory -Force -ErrorAction Stop }
|
||||||
|
Catch { Write-Error $_.Exception.Message ; Return }
|
||||||
|
|
||||||
|
# Write $CONFIG to config.xml
|
||||||
|
$CONFIGFILE = "${TEMPDIR}\config.xml"
|
||||||
|
Try { $CONFIG | WriteToFile_UTF8NoBOM -FilePath "${CONFIGFILE}" }
|
||||||
|
Catch { Write-Output "Cannot write configuration file! This script will exit." ; Write-Error $_.Exception.Message ; Return }
|
||||||
|
|
||||||
|
# Set the name of ODT setup utility
|
||||||
|
$ODT = "${TEMPDIR}\setup.exe"
|
||||||
|
|
||||||
|
# Download ODT
|
||||||
|
Write-Output "Downloading Office Deployment Tool"
|
||||||
|
Download-File -URL 'https://xfer.emberkom.com/shares/tools/folders/9b9988fb-c53a-4833-a4ad-68d01a3021bb/files/setup-exe/download/setup.exe' -File $ODT
|
||||||
|
|
||||||
|
# Install Office
|
||||||
|
Write-Output "Installing Microsoft Office"
|
||||||
|
Try { Start-Process $ODT -ArgumentList "/configure ""${CONFIGFILE}""" -Wait }
|
||||||
|
Catch { Write-Error $_.Exception.Message }
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
Write-Output "Deleting temp directory and its contents"
|
||||||
|
Try { Remove-Item -Path $TEMPDIR -Recurse -Force -ErrorAction SilentlyContinue }
|
||||||
|
Catch { Write-Error $_.Exception.Message }
|
||||||
+15
-19
@@ -1,24 +1,20 @@
|
|||||||
# $InstallVersion must be set by the calling script or specified here
|
|
||||||
|
|
||||||
# Make sure we have an administrative token
|
# This is the URL for retreving the latest version of the specified installer
|
||||||
If ( IsAdmin )
|
# Trying to query this URL directly no longer works now that CloudFlare is blocking script access
|
||||||
{
|
# It does, however, work in the browser to retrieve the direct download link
|
||||||
# Define URL for downloading the installer
|
# https://www.sketchup.com/sketchup/2023/SketchUpPro-exe
|
||||||
$URL = "https://www.sketchup.com/sketchup/${InstallVersion}/SketchUpPro-exe"
|
|
||||||
|
|
||||||
# Make sure the URL is valid before we do anything
|
# Define URL for downloading the installer
|
||||||
If ( IsURLValid $URL )
|
$URL = 'https://download.sketchup.com/SketchUpStudio-2023-1-340-117.exe'
|
||||||
{
|
|
||||||
# TODO: Uninstall all versions of SketchUp
|
|
||||||
# 'SketchUp 2019', 'SketchUp 2021', 'SketchUp 2022', 'SketchUp 2023', 'SketchUp Viewer' | Uninstall-MSI
|
|
||||||
|
|
||||||
# Set the local file name to download to
|
# TODO: Uninstall all versions of SketchUp
|
||||||
$FILE = '{0}{1}.exe' -f (Get-TempPath),(Split-Path $URL -Leaf)
|
#'SketchUp 2019', 'SketchUp 2020', 'SketchUp 2021', 'SketchUp 2022', 'SketchUp 2023', 'SketchUp Viewer' | Uninstall-MSI
|
||||||
|
|
||||||
# Download installer
|
# Set the local file name to download to
|
||||||
$FILE = Download-File -URL $URL -File $FILE
|
$FILE = '{0}{1}' -f (Get-TempPath),(Split-Path $URL -Leaf)
|
||||||
|
|
||||||
# Install package
|
# Download installer
|
||||||
Install-Program -Path $FILE -Arguments "/silent" -Delete
|
$FILE = Download-File -URL $URL -File $FILE
|
||||||
}
|
|
||||||
}
|
# Install package
|
||||||
|
Install-Program -Path $FILE -Arguments "/silent" -Delete
|
||||||
+27
-27
@@ -1,31 +1,31 @@
|
|||||||
param(
|
# This script is intended to be called from a Windows Provisioning Package.
|
||||||
[Parameter(Mandatory=$true)][string]$URL,
|
# The provisioning package invokes this script the following way:
|
||||||
[Parameter(Mandatory=$false)][string]$AGENT_FILENAME="SyncroInstaller.msi",
|
# powershell.exe -ExecutionPolicy Bypass -Command ". $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Install-SyncroAgent.ps1'))) ; Install-SyncroAgent -URL 'https://install.syncromsp.com/agent.msi'"
|
||||||
[Parameter(Mandatory=$false)][switch]$FORCE=$false
|
|
||||||
)
|
|
||||||
|
|
||||||
# Delete this script
|
Function Install-SyncroAgent ([string]$URL) {
|
||||||
Remove-Item $MyInvocation.MyCommand.Path -Force
|
|
||||||
|
|
||||||
# Exit early if we don't need to install the agent
|
# Exit if $URL is null or empty
|
||||||
If ( ($FORCE -eq $false) -and ((Test-Path "${Env:ProgramFiles}\RepairTech\Syncro\Syncro.App.Runner.exe") -or (Test-Path "${Env:ProgramFiles(x86)}\RepairTech\Syncro\Syncro.App.Runner.exe")) ) { Exit }
|
If ([string]::IsNullOrEmpty($URL)) {
|
||||||
|
Write-Output "The URL parameter cannot be null or empty"
|
||||||
|
Exit 1
|
||||||
|
}
|
||||||
|
|
||||||
Function SourceTools
|
# Exit if running without admin rights
|
||||||
{ . $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1'))) }
|
If (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
|
||||||
. SourceTools
|
Write-Output "This script must be run as an administrator"
|
||||||
|
Exit 1
|
||||||
|
}
|
||||||
|
|
||||||
# Build the path for $AGENT_INSTALLER
|
# Download and install the Syncro Agent
|
||||||
$AGENT_INSTALLER = '{0}{1}' -f (Get-TempPath), $AGENT_FILENAME
|
$Installer = "${Env:TEMP}\SyncroInstaller.msi"
|
||||||
|
Try { (New-Object System.Net.WebClient).DownloadFile($URL,$Installer) }
|
||||||
# Download agent installer
|
Catch { Write-Error "The Syncro Agent installer could not be downloaded from ${URL}`n"+$_.Exception.Message ; Exit 1 }
|
||||||
Download-File -URL $URL -File "${AGENT_INSTALLER}"
|
If ( Test-Path $Installer ) {
|
||||||
|
Start-Process msiexec.exe -ArgumentList "/i ""${Installer}"" /qn /norestart" -Wait
|
||||||
# Install the agent
|
Remove-Item $Installer -Force -ErrorAction SilentlyContinue
|
||||||
Write-Output "Installing Syncro agent from ""${AGENT_INSTALLER}"""
|
} Else {
|
||||||
Try { Start-Process "msiexec.exe" -ArgumentList "/i ""${AGENT_INSTALLER}"" /qn /norestart" -Wait }
|
Write-Error "The Syncro Agent installer was successfully downloaded but could not be found at ""${Installer}"""
|
||||||
Catch { Write-Error $_.Exception.Message }
|
Exit 1
|
||||||
|
}
|
||||||
# Delete the agent installer
|
Exit 0
|
||||||
Write-Output "Deleting ""${AGENT_INSTALLER}"""
|
}
|
||||||
Try { Remove-Item $AGENT_INSTALLER -Force }
|
|
||||||
Catch { Write-Error $_.Exception.Message }
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ If ( IsAdmin )
|
|||||||
|
|
||||||
## Create shortcut on Public desktop
|
## Create shortcut on Public desktop
|
||||||
If ( $CreateDesktopShortcut ) {
|
If ( $CreateDesktopShortcut ) {
|
||||||
Create-Shortcut -Path "${Env:Public}\Desktop\WireGuard VPN.LNK" -TargetPath "${Env:ProgramFiles}\WireGuard\wireguard.exe" -Description "WireGuard: Fast, Modern, Secure VPN Tunnel"
|
New-Shortcut -Path "${Env:Public}\Desktop\WireGuard VPN.LNK" -TargetPath "${Env:ProgramFiles}\WireGuard\wireguard.exe" -Description "WireGuard: Fast, Modern, Secure VPN Tunnel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Else { Write-Output "Must be an administrator to install WireGuard" }
|
Else { Write-Output "Must be an administrator to install WireGuard" }
|
||||||
@@ -1,9 +1,67 @@
|
|||||||
# Management Scripts
|
# Management Scripts
|
||||||
|
|
||||||
Most scripts here require the Tools.ps1 script. You can dot source that script like this:
|
Most scripts here require the Tools.ps1 script. You can dot source that script like this:
|
||||||
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
|
||||||
|
```powershell
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
```
|
||||||
|
|
||||||
|
On older Windows PowerShell/.NET configurations, select TLS 1.2 **before** the first HTTPS download. The TLS setting inside `Tools.ps1` cannot help download `Tools.ps1` itself. See [Microsoft's PowerShell TLS guidance](https://learn.microsoft.com/en-us/security/engineering/solving-tls1-problem#update-windows-powershell-scripts-or-related-registry-settings).
|
||||||
|
|
||||||
You can then run any of the scripts using the Run-Script function:
|
You can then run any of the scripts using the Run-Script function:
|
||||||
Run-Script -LivePSScript Script-Name
|
Run-Script -LivePSScript Script-Name
|
||||||
|
|
||||||
If you're using an RMM tool, the scripts in the /rmm directory are all that's needed to hook the main script in this directory.
|
If you're using an RMM tool, the scripts in the /rmm directory are all that's needed to hook the main script in this directory.
|
||||||
|
|
||||||
|
For DSA collection, paste [rmm/Get-DSAManifest.ps1](rmm/Get-DSAManifest.ps1) into the RMM caller, preserving its configured platform variables. This caller selects TLS 1.2 before downloading, stops on bootstrap failure, and dot-sources the downloaded tools in the caller's scope. Updating the repository alone will not change an existing RMM caller.
|
||||||
|
|
||||||
|
For download failures, use the standalone [Test-FileDownload.ps1](Test-FileDownload.ps1) diagnostic. See [the RMM caller, report guide, and shared caller audit](docs/FileDownload-Diagnostics.md). It compares the existing helper, BITS, and direct GET without changing production download behavior or running downloaded files.
|
||||||
|
|
||||||
|
`Get-DSAManifest.ps1` uses `Download-FileDirectly`, which passes the original URL to BITS without preliminary `Get-AbsoluteURI` or `IsURLValid` requests. It accepts the same URL and optional `-File` arguments as `Download-File`, waits for completion, and throws on BITS errors. If `-File` is omitted, its temporary filename comes from the original URL; supply `-File` for extensionless URLs or URLs with query strings. Existing `Download-File` callers retain their previous behavior. Publish `Tools.ps1` together with `Get-DSAManifest.ps1` so the new helper is available.
|
||||||
|
|
||||||
|
## Converting RMM boolean variables
|
||||||
|
|
||||||
|
After dot-sourcing `Tools.ps1`, use `ConvertTo-RmmBoolean` with any RMM variable:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$RestartEnabled = ConvertTo-RmmBoolean -Value $Restart -VariableName 'Restart'
|
||||||
|
If ( $RestartEnabled ) {
|
||||||
|
# Perform the requested restart.
|
||||||
|
}
|
||||||
|
|
||||||
|
# The variable name is optional; positional and pipeline input also work.
|
||||||
|
$ZipEnabled = ConvertTo-RmmBoolean $Zip
|
||||||
|
$UploadEnabled = $Upload | ConvertTo-RmmBoolean
|
||||||
|
```
|
||||||
|
|
||||||
|
Each input returns a `System.Boolean`: `true`, `$true`, `yes`, `y`, and `1` become `$true`; `false`, `$false`, `no`, `n`, `0`, blank strings, and `$null` become `$false`. Case and surrounding whitespace are ignored, and native boolean values also work. Other values throw an error; `-VariableName` identifies the input in that error. The function returns the converted value without changing the original variable; assign the result wherever needed.
|
||||||
|
|
||||||
|
`Get-DSAManifest.ps1` uses this helper for its three options. Publish it together with the updated `Tools.ps1` so the helper is available to the RMM caller.
|
||||||
|
|
||||||
|
## Uploading files
|
||||||
|
|
||||||
|
After dot-sourcing `Tools.ps1`, scripts can upload a completed file to the Emberkom FileDrop:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Upload-File -Path 'C:\ProgramData\Emberkom\Output\report.zip'
|
||||||
|
```
|
||||||
|
|
||||||
|
`Get-DSAManifest.ps1` uploads its completed CSV or ZIP when `$Upload` is enabled. If `$DeleteAfterUpload` is also enabled, it deletes that same local file only after a confirmed successful upload. Disabled or failed uploads retain the file. `$Zip`, `$Upload`, and `$DeleteAfterUpload` all accept `true`, `$true`, `yes`, `y`, or `1`; `false`, `$false`, `no`, `n`, `0`, or an empty value disable the option. Casing and surrounding whitespace are ignored. The notification subject identifies the computer, and its message includes the source path and collection start time (local time with UTC offset).
|
||||||
|
|
||||||
|
The default destination is `https://xfer.emberkom.com/filedrop/cmd`. The sender defaults to the computer's hostname plus its primary DNS suffix, followed by `@emberkom.com` (for example, `pc01.example.com@emberkom.com`). Without a DNS suffix, it uses the hostname. Override these with `-FileDropUrl` and `-From`; `-Subject` and `-Message` are also optional.
|
||||||
|
|
||||||
|
`Upload-File` uses the [LiquidFiles FileDrop API](https://docs.liquidfiles.com/api/v4.3/filedrop/) to obtain a temporary token, upload the file, and submit the message that triggers FileDrop delivery and its configured email notification. It returns a receipt with the path, destination, sender, attachment ID, byte count, and server status. It retains the local file and throws on failure.
|
||||||
|
|
||||||
|
Uploads use [binary chunks](https://docs.liquidfiles.com/api/v4.3/attachments/chunks.html) with a default 10 MiB buffer, so large files do not need to fit in memory. The FileDrop's size and extension restrictions are checked before upload. `-ChunkSizeMB`, `-RetryCount`, and `-TimeoutSeconds` control chunk size, retries, and each request's timeout; `-Verbose` displays transfer details.
|
||||||
|
|
||||||
|
Allow the RMM job to run for the entire upload. The function waits for completion and retries failed chunks within that run; it does not resume across process restarts. LiquidFiles tokens expire after 24 hours. Final submission is attempted once: if its response is lost, check the FileDrop before rerunning to avoid duplicate notifications.
|
||||||
|
|
||||||
|
Offline tests (no uploads or notifications):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell.exe -NoProfile -File .\tests\Upload-File.Tests.ps1
|
||||||
|
powershell.exe -NoProfile -File .\tests\Get-DSAManifest.Tests.ps1
|
||||||
|
powershell.exe -NoProfile -File .\tests\Download-File.Tests.ps1
|
||||||
|
powershell.exe -NoProfile -File .\tests\ConvertTo-RmmBoolean.Tests.ps1
|
||||||
|
```
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Make sure we're booted into Safe Mode with Networking
|
||||||
|
If ( !((Get-CimInstance -ClassName Win32_ComputerSystem).BootupState.ToLower() -eq "fail-safe with network boot") ) {
|
||||||
|
Write-Output "Cannot run script because the system is not currently booted into Safe Mode with Networking"
|
||||||
|
Return
|
||||||
|
}
|
||||||
|
|
||||||
|
# Download the ESET Uninstaller
|
||||||
|
Write-Output "Downloading ESET Uninstaller to: ${ESETUninstaller}"
|
||||||
|
$ESETUninstallerURL = "https://download.eset.com/com/eset/tools/installers/eset_apps_remover/latest/esetuninstaller.exe"
|
||||||
|
Download-File -URL $ESETUninstallerURL -File $ESETUninstaller
|
||||||
|
If ( !(Test-Path $ESETUninstaller) ) { Write-Error "ERROR: ESET Uninstaller could not be downloaded, this script will exit" ; Return }
|
||||||
|
|
||||||
|
# Backup network interfaces and settings
|
||||||
|
$InterfacesFile = "${Global:OutputDirectory}\interfaces.dat"
|
||||||
|
If ( Test-Path $InterfacesFile ) { Write-Output "Removing existing interface export: ${InterfacesFile}" ; Remove-Item -Path $InterfacesFile -Force }
|
||||||
|
Write-Output "Exporting interfaces to: ${InterfacesFile}"
|
||||||
|
Try { & netsh.exe -c interface dump | Out-File $InterfacesFile }
|
||||||
|
Catch { Write-Error "ERROR: Interfaces could not be exported, this script will exit" ; Return }
|
||||||
|
|
||||||
|
# Uninstall all ESET products
|
||||||
|
Write-Output "Uninstalling all ESET products"
|
||||||
|
& "cmd.exe /k ""${ESETUninstaller}"" /mode=online /force"
|
||||||
|
|
||||||
|
# Restore network interfaces and settings
|
||||||
|
If ( Test-Path $InterfacesFile ) {
|
||||||
|
Write-Output "Restoring interfaces from export: ${InterfacesFile}"
|
||||||
|
Try { & netsh.exe exec $InterfacesFile }
|
||||||
|
Catch { Write-Error "ERROR: Interfaces could not be restored!" }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ensure safe mode is disabled
|
||||||
|
Write-Output "Disabling safe mode"
|
||||||
|
$command = "${Env:SystemRoot}\System32\bcdedit.exe"
|
||||||
|
$params = "/deletevalue {default} safeboot".Split(" ")
|
||||||
|
Try { & "${command}" $params }
|
||||||
|
Catch {
|
||||||
|
If ( $_.Exception.Message -match "Element not found" ) { Write-Output " - Safe mode has already been disabled" }
|
||||||
|
Else { Write-Error "ERROR: could not disable safe mode, manual intervention may be required" }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Reboot into normal mode
|
||||||
|
Write-Output "Rebooting into normal mode"
|
||||||
|
& "${Env:SystemRoot}\System32\shutdown.exe" -r -f -t 60 -c "Operation complete. The computer will restart in 60 seconds."
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Define the username of the provisioning admin account
|
||||||
|
$ProvisioningAdminUsername = "ek-provadmin"
|
||||||
|
|
||||||
|
# Exit if this is a domain controller
|
||||||
|
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"
|
||||||
|
Exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the provisioning admin user account
|
||||||
|
$ProvisioningAdmin = Get-LocalUser -Name $ProvisioningAdminUsername -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# Exit if the provisioning admin account does not exist
|
||||||
|
If ( $ProvisioningAdmin -eq $false ) { Write-Output "Provisioning Admin account does not exist" ; Exit 0 }
|
||||||
|
|
||||||
|
# Exit if the user is logged in
|
||||||
|
If ( $(Get-CimInstance -Class Win32_Process -Filter 'name = "explorer.exe"' | Invoke-CimMethod -MethodName getowner).User -contains $ProvisioningAdminUsername ) {
|
||||||
|
Write-Output "User is logged in, cannot complete removal of provisioning admin account"
|
||||||
|
Exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove the provisioning admin account
|
||||||
|
$SID = $ProvisioningAdmin.SID.Value
|
||||||
|
Write-Output "Removing provisioning admin account`nUsername: ${ProvisioningAdminUsername}`nSID: $SID"
|
||||||
|
Try { Remove-LocalUser -SID $SID -ErrorAction Stop }
|
||||||
|
Catch { Write-Error "The provisioning admin account could not be removed`n"+$_.Exception.Message ; Exit 1 }
|
||||||
|
|
||||||
|
# Fully remove the provisioning admin profile
|
||||||
|
Write-Output "Removing provisioning admin CIM instance"
|
||||||
|
Try { Get-CimInstance -Class Win32_UserProfile | Where-Object { $_.SID -eq $SID } -ErrorAction SilentlyContinue | Remove-CimInstance }
|
||||||
|
Catch { Write-Error "The provisioning admin profile could not be removed`n"+$_.Exception.Message ; Exit 1 }
|
||||||
|
|
||||||
|
# Delete any remaining provisioning admin directories
|
||||||
|
$ProfilePath = "${Env:SystemDrive}\Users\$ProvisioningAdminUsername"
|
||||||
|
If ( Test-Path $ProfilePath ) {
|
||||||
|
Write-Output "Deleting provisioning admin profile directory: ${ProfilePath}"
|
||||||
|
Try { Remove-Item $ProfilePath -Recurse -Force }
|
||||||
|
Catch { Write-Error "The provisioning admin profile directory could not be removed`n"+$_.Exception.Message ; Exit 1 }
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Function to restart an endpoint that has not been restarted for the specified number of hours
|
||||||
|
Function Restart-EndpointOnUptime ([int]$MaxUptimeHours) {
|
||||||
|
|
||||||
|
# Exit if the specified number of hours is not greater than 0
|
||||||
|
If ( !($MaxUptimeHours -gt 0) ) { Write-Output "Must specify 1 or more hours" ; Exit }
|
||||||
|
|
||||||
|
# Get the last boot up time as a DateTime
|
||||||
|
$LastBootTime = [Management.ManagementDateTimeConverter]::ToDateTime($(Get-WmiObject -Class Win32_OperatingSystem -ErrorAction Stop | Select-Object -ExpandProperty LastBootUpTime))
|
||||||
|
|
||||||
|
# Get the total number of hours the endpoint has been up
|
||||||
|
$UptimeHours = $($(Get-Date) - $LastBootTime).TotalHours
|
||||||
|
|
||||||
|
# If $Uptime is greater than $MaxUptimeHours, restart the endpoint
|
||||||
|
If ( $UptimeHours -gt $MaxUptimeHours) {
|
||||||
|
Write-Output "This endpoint has been up for at least ${UptimeHours} hours and will be restarted now"
|
||||||
|
Start-Sleep -Seconds 5
|
||||||
|
Restart-Computer -Force
|
||||||
|
} Else {
|
||||||
|
Write-Output "This endpoint has already been restarted within the last ${MaxUptimeHours} hours"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
Write-Output "Setting local Administrator password"
|
||||||
|
Try { net user Administrator $Password /times:all /active:yes }
|
||||||
|
Catch { Write-Error "Could not set local Administrator password`n"+$_.Exception.Message ; Exit 1 }
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
$LogsToEnable = @(
|
|
||||||
''
|
|
||||||
)
|
|
||||||
|
|
||||||
ForEach ( $log in $LogsToEnable ) {
|
|
||||||
Toggle-Log -Name $log -Enable
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# Exit if not running as administrator
|
||||||
|
If ( -not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") ) { Write-Output "This script must be run as an administrator" ; Exit 1 }
|
||||||
|
|
||||||
|
# Get the operating system CIM instance
|
||||||
|
Try { $OS = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop }
|
||||||
|
Catch { Write-Error "Could not get Win32_OperatingSystem CIM instance`n"+$_.Exception.Message ; Exit 1 }
|
||||||
|
|
||||||
|
# Exit if the endpoint is not running Windows 10
|
||||||
|
If ( ($OS.Caption -notmatch "Windows 10") ) { Write-Output "This endpoint is not running Windows 10" ; Exit 1 }
|
||||||
|
Else { Write-Output "This endpoint is running Windows 10" }
|
||||||
|
|
||||||
|
# Exit if the endpoint is not running a Pro/Enterprise edition of Windows
|
||||||
|
If ( ($OS.Caption -notmatch "Pro") -and ($OS.Caption -notmatch "Enterprise") ) { Write-Output "Only Pro/Enterprise versions of Windows can be upgraded" ; Exit 1 }
|
||||||
|
Else { Write-Output "This endpoint is running a Pro/Enterprise version of Windows" }
|
||||||
|
|
||||||
|
# Exit if the endpoint is a server edition
|
||||||
|
Try
|
||||||
|
{
|
||||||
|
If ( $OS.ProductType -gt 1 ) { Write-Output "Server operating system will not be upgraded" ; Exit 1 }
|
||||||
|
Else { Write-Output "This endpoint is not a server operating system" }
|
||||||
|
}
|
||||||
|
Catch { Write-Error "The operating system type could not be determined`n"+$_.Exception.Message ; Exit 1 }
|
||||||
|
|
||||||
|
# Exit if the endpoint does not have at least 4GB of RAM
|
||||||
|
Try
|
||||||
|
{
|
||||||
|
If ( (Get-CimInstance -ClassName Win32_PhysicalMemory -ErrorAction Stop | Measure-Object -Property Capacity -Sum).Sum -lt 4GB ) { Write-Output "This endpoint does not have at least 4GB of RAM" ; Exit 1 }
|
||||||
|
Else { Write-Output "This endpoint has at least 4GB of RAM" }
|
||||||
|
}
|
||||||
|
Catch { Write-Error "The amount of RAM could not be determined`n"+$_.Exception.Message ; Exit 1 }
|
||||||
|
|
||||||
|
# Exit if the endpoint does not have at least 64GB of storage on the system drive
|
||||||
|
Try
|
||||||
|
{
|
||||||
|
$SystemDrive = $Env:SystemDrive.TrimEnd('\')
|
||||||
|
If ( (Get-CimInstance -ClassName Win32_Volume -Filter "DriveLetter = '$SystemDrive'" -ErrorAction Stop).FreeSpace -lt 64GB ) { Write-Output "This endpoint does not have at least 64GB of storage on the system drive" ; Exit 1 }
|
||||||
|
Else { Write-Output "This endpoint has at least 64GB of free space on the system drive" }
|
||||||
|
}
|
||||||
|
Catch { Write-Error "The free space on the system drive could not be determined`n"+$_.Exception.Message ; Exit 1 }
|
||||||
|
|
||||||
|
# Exit if TPM does not exist or is not version 2.0
|
||||||
|
Try
|
||||||
|
{
|
||||||
|
$TPM2Present = $false
|
||||||
|
ForEach ( $specVersion in (Get-CimInstance -Namespace "root\CIMV2\Security\MicrosoftTpm" -Class Win32_Tpm -ErrorAction Stop).SpecVersion )
|
||||||
|
{
|
||||||
|
If ( $specVersion -ge 2.0 ) { $TPM2Present = $true ; Break }
|
||||||
|
}
|
||||||
|
If ( $TPM2Present -eq $false ) { Write-Output "TPM does not support version 2.0 specification" ; Exit 1 }
|
||||||
|
Else { Write-Output "This endpoint has a TPM compliant with the version 2.0 specification" }
|
||||||
|
}
|
||||||
|
Catch { Write-Error "TPM is not present or its version cannot be determined`n"+$_.Exception.Message ; Exit 1 }
|
||||||
|
|
||||||
|
# Exit if Secure Boot is not enabled
|
||||||
|
Try
|
||||||
|
{
|
||||||
|
If ( (Confirm-SecureBootUEFI -ErrorAction Stop) -eq $false ) { Write-Output "Secure Boot is not enabled" ; Exit 1 }
|
||||||
|
Else { Write-Output "This endpoint has Secure Boot enabled" }
|
||||||
|
}
|
||||||
|
Catch { Write-Output "Secure Boot state cannot be determined.`n"+$_.Exception.Message ; Exit 1 }
|
||||||
|
|
||||||
|
# Uninstall previous Windows 11 Update Assistant
|
||||||
|
$WIAPath = "${Env:ProgramFiles(x86)}\WindowsInstallationAssistant\Windows10UpgraderApp.exe"
|
||||||
|
If ( Test-Path $WIAPath )
|
||||||
|
{
|
||||||
|
Write-Output "Uninstalling existing Windows Installation Assistant"
|
||||||
|
Get-Process -Name "Windows10UpgraderApp" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
|
||||||
|
Try { Start-Process $WIAPath -ArgumentList '/ForceUninstall' -Wait }
|
||||||
|
Catch { Write-Error $_.Exception.Message ; Exit 1}
|
||||||
|
} Else { Write-Output "This endpoint does not have the Windows Installation Assistant installed" }
|
||||||
|
|
||||||
|
# URL to the Windows Installation Assistant
|
||||||
|
$URL = 'https://go.microsoft.com/fwlink/?linkid=2171764'
|
||||||
|
|
||||||
|
# Download Windows Installation Assistant
|
||||||
|
$WIAInstaller = "${Env:TEMP}\Windows11InstallationAssistant.exe"
|
||||||
|
If ( Test-Path $WIAInstaller )
|
||||||
|
{
|
||||||
|
Write-Output "Removing existing Windows Installation Assistant installer: ${WIAInstaller}"
|
||||||
|
Remove-Item $WIAInstaller -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
Write-Output "Downloading ${URL} to ""${WIAInstaller}"""
|
||||||
|
Try { (New-Object System.Net.WebClient).DownloadFile($URL,$WIAInstaller) }
|
||||||
|
Catch { Write-Error "The Windows Installation Assistant could not be downloaded`n"+$_.Exception.Message ; Exit 1 }
|
||||||
|
|
||||||
|
# Install and run the Windows Installation Assistant
|
||||||
|
If ( -not (Test-Path $WIAInstaller) ) { Write-Error "The Windows Installation Assistant could not be found after successfully downloading" ; Exit 1 }
|
||||||
|
Write-Output "Launching upgrade"
|
||||||
|
Try { Start-Process -FilePath $WIAInstaller -ArgumentList "/QuietInstall /SkipEULA /Auto Upgrade /SkipSelfUpdate /ShowProgressInTaskBarIcon /SkipCompatCheck" }
|
||||||
|
Catch { Write-Output $_.Exception.Message ; Exit 1 }
|
||||||
|
Exit 0
|
||||||
@@ -0,0 +1,540 @@
|
|||||||
|
#Requires -Version 2.0
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Compares download paths without changing the production downloader or TLS settings.
|
||||||
|
.DESCRIPTION
|
||||||
|
Run from the failing RMM caller, after its normal Tools.ps1 bootstrap, using the
|
||||||
|
same account and PowerShell executable. A standalone run skips the helper probe
|
||||||
|
when Download-File is not loaded. Never dot-sources a downloaded payload.
|
||||||
|
|
||||||
|
Each probe runs in a bounded child process. Only that run's BITS jobs and scratch
|
||||||
|
files are removed. Reports remain in a new subdirectory of OutputDirectory.
|
||||||
|
The source deliberately uses PowerShell 2 syntax and .NET 2-era APIs. Actual
|
||||||
|
PowerShell 2, 3, and 4 runtime validation is still required on legacy machines.
|
||||||
|
.PARAMETER URL
|
||||||
|
HTTP or HTTPS file URL. Downloads are never executed.
|
||||||
|
.PARAMETER OutputDirectory
|
||||||
|
Report parent directory; defaults to the account's temporary directory.
|
||||||
|
.PARAMETER TimeoutSeconds
|
||||||
|
Wall-clock limit for each probe, including download and hashing. Defaults to 120.
|
||||||
|
.EXAMPLE
|
||||||
|
& .\Test-FileDownload.ps1 -URL 'https://example.com/tool.exe'
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true, Position=0)][string]$URL,
|
||||||
|
[string]$OutputDirectory = [IO.Path]::GetTempPath(),
|
||||||
|
[ValidateRange(1,3600)][int]$TimeoutSeconds = 120
|
||||||
|
)
|
||||||
|
|
||||||
|
# All diagnostic state is local even when the caller dot-sources this script.
|
||||||
|
& {
|
||||||
|
param($FdUrlText, $FdOutputParent, $FdTimeout)
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$FdUri = New-Object Uri $FdUrlText
|
||||||
|
if (!$FdUri.IsAbsoluteUri -or @('http','https') -notcontains $FdUri.Scheme -or $FdUri.UserInfo) {
|
||||||
|
throw 'URL must be an absolute HTTP(S) URL without embedded credentials.'
|
||||||
|
}
|
||||||
|
if ($PSVersionTable.PSVersion.Major -gt 5) { throw 'Run this diagnostic in Windows PowerShell 2-5.1, using the same shell as the RMM.' }
|
||||||
|
$FdRunId = [guid]::NewGuid().ToString('N')
|
||||||
|
$FdParent = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FdOutputParent)
|
||||||
|
$FdRoot = Join-Path $FdParent ('file-download-diagnostic-' + $FdRunId)
|
||||||
|
$null = New-Item -Path $FdRoot -ItemType Directory -ErrorAction Stop
|
||||||
|
$FdWork = Join-Path $FdRoot 'work'
|
||||||
|
$null = New-Item -Path $FdWork -ItemType Directory -ErrorAction Stop
|
||||||
|
$FdExe = [Diagnostics.Process]::GetCurrentProcess().MainModule.FileName
|
||||||
|
if ([IO.Path]::GetFileName($FdExe) -ine 'powershell.exe') {
|
||||||
|
throw 'Run from powershell.exe rather than ISE or an embedded host so child probes can use the same executable safely.'
|
||||||
|
}
|
||||||
|
$FdExtension = [IO.Path]::GetExtension($FdUri.AbsolutePath)
|
||||||
|
if ($FdExtension -notmatch '^\.[A-Za-z0-9]{1,16}$') { $FdExtension = '.download' }
|
||||||
|
|
||||||
|
function Get-FdTextHash($Text) {
|
||||||
|
$FdHash = [Security.Cryptography.SHA256]::Create()
|
||||||
|
try { return [BitConverter]::ToString($FdHash.ComputeHash([Text.Encoding]::UTF8.GetBytes($Text))).Replace('-','') }
|
||||||
|
finally { $FdHash.Clear() }
|
||||||
|
}
|
||||||
|
function Export-FdXml($Value,$Path,$Depth) {
|
||||||
|
# PowerShell 2 lacks some LiteralPath parameter sets. A scoped drive keeps
|
||||||
|
# wildcard characters in the parent directory out of Export-Clixml's path.
|
||||||
|
$Drive = 'FdXml' + [guid]::NewGuid().ToString('N')
|
||||||
|
$null = New-PSDrive -Name $Drive -PSProvider FileSystem -Root ([IO.Path]::GetDirectoryName($Path)) -Scope Local
|
||||||
|
try { $Value | Export-Clixml -Path ($Drive + ':\' + [IO.Path]::GetFileName($Path)) -Depth $Depth }
|
||||||
|
finally { Remove-PSDrive -Name $Drive -Scope Local }
|
||||||
|
}
|
||||||
|
|
||||||
|
$FdCommands = @()
|
||||||
|
foreach ($FdName in @('Download-File','Get-AbsoluteURI','IsURLValid','Get-TempPath','Write-Error','Write-Output','Start-BitsTransfer')) {
|
||||||
|
$FdCommand = Get-Command -Name $FdName -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||||
|
$FdDefinition = $null
|
||||||
|
if ($FdCommand -and @('Function','Filter') -contains [string]$FdCommand.CommandType) { $FdDefinition = $FdCommand.Definition }
|
||||||
|
$FdCommands += New-Object PSObject -Property @{
|
||||||
|
Name=$FdName; CommandType=[string]$FdCommand.CommandType; ModuleName=$FdCommand.ModuleName
|
||||||
|
ModuleVersion=[string]$FdCommand.Module.Version; Definition=$FdDefinition
|
||||||
|
DefinitionSHA256=$(if ($null -ne $FdDefinition) { Get-FdTextHash $FdDefinition } else { $null })
|
||||||
|
ResolvedDefinition=$FdCommand.Definition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$FdContext = New-Object PSObject -Property @{
|
||||||
|
RunId=$FdRunId; URL=$FdUri.AbsoluteUri; Commands=$FdCommands
|
||||||
|
SecurityProtocol=[int][Net.ServicePointManager]::SecurityProtocol
|
||||||
|
CertificateCallbackPresent=($null -ne [Net.ServicePointManager]::ServerCertificateValidationCallback)
|
||||||
|
ProxyType=$(if ([Net.WebRequest]::DefaultWebProxy) { [Net.WebRequest]::DefaultWebProxy.GetType().FullName } else { 'None' })
|
||||||
|
ProxyAddress=[string]([Net.WebRequest]::DefaultWebProxy.Address)
|
||||||
|
PowerShellVersion=[string]$PSVersionTable.PSVersion; CLRVersion=[string][Environment]::Version
|
||||||
|
ProcessBits=([IntPtr]::Size * 8); Executable=$FdExe
|
||||||
|
Identity=[Security.Principal.WindowsIdentity]::GetCurrent().Name
|
||||||
|
WorkingDirectory=(Get-Location).Path; TimeoutSeconds=$FdTimeout; DestinationExtension=$FdExtension
|
||||||
|
}
|
||||||
|
$FdContextPath = Join-Path $FdWork 'context.clixml'
|
||||||
|
Export-FdXml $FdContext $FdContextPath 12
|
||||||
|
|
||||||
|
# This is diagnostic code, not code fetched from the URL. It is copied into each
|
||||||
|
# fresh child so leaked connections and preference changes cannot cross probes.
|
||||||
|
$FdWorker = {
|
||||||
|
param($InputPath)
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
|
$InputData = Import-Clixml -Path ([Management.Automation.WildcardPattern]::Escape($InputPath))
|
||||||
|
$Context = Import-Clixml -Path ([Management.Automation.WildcardPattern]::Escape($InputData.ContextPath))
|
||||||
|
$Result = New-Object PSObject -Property @{
|
||||||
|
Name=$InputData.Name; Method=$InputData.Method; ExistingEmpty=$InputData.ExistingEmpty
|
||||||
|
Status='Running'; StartedUtc=[DateTime]::UtcNow.ToString('o'); ElapsedSeconds=0
|
||||||
|
Errors=@(); Notes=@(); Http=@(); Bits=@(); Snapshots=@(); HelperOutput=@(); HelperBitsSource=@(); Environment=$null
|
||||||
|
CleanupErrors=@(); ChildPowerShell=[string]$PSVersionTable.PSVersion
|
||||||
|
ChildCLR=[string][Environment]::Version; ChildIdentity=[Security.Principal.WindowsIdentity]::GetCurrent().Name
|
||||||
|
ChildProcessBits=([IntPtr]::Size * 8)
|
||||||
|
}
|
||||||
|
$Watch = [Diagnostics.Stopwatch]::StartNew()
|
||||||
|
function Export-FdXml($Value,$Path,$Depth) {
|
||||||
|
$Drive = 'FdXml' + [guid]::NewGuid().ToString('N')
|
||||||
|
$null = New-PSDrive -Name $Drive -PSProvider FileSystem -Root ([IO.Path]::GetDirectoryName($Path)) -Scope Local
|
||||||
|
try { $Value | Export-Clixml -Path ($Drive + ':\' + [IO.Path]::GetFileName($Path)) -Depth $Depth }
|
||||||
|
finally { Remove-PSDrive -Name $Drive -Scope Local }
|
||||||
|
}
|
||||||
|
function Save-Checkpoint {
|
||||||
|
$Result.ElapsedSeconds = [Math]::Round($Watch.Elapsed.TotalSeconds,3)
|
||||||
|
# Replace only this probe's checkpoint; the parent reads it after child exit.
|
||||||
|
Export-FdXml $Result $InputData.ResultPath 16
|
||||||
|
}
|
||||||
|
function Get-ErrorDetail($Record) {
|
||||||
|
return New-Object PSObject -Property @{
|
||||||
|
Message=$Record.Exception.Message; Exception=$Record.Exception.ToString()
|
||||||
|
FullyQualifiedErrorId=$Record.FullyQualifiedErrorId
|
||||||
|
Category=[string]$Record.CategoryInfo; Position=$Record.InvocationInfo.PositionMessage
|
||||||
|
Record=($Record | Format-List * -Force | Out-String -Width 240)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function Get-FileSnapshot($Label) {
|
||||||
|
$Snapshot = New-Object PSObject -Property @{Label=$Label; TimeUtc=[DateTime]::UtcNow.ToString('o'); Exists=$false; Length=$null; LengthAfterHash=$null; SHA256=$null; FirstBytes=$null; Error=$null}
|
||||||
|
$Stream = $null; $Hash = $null
|
||||||
|
try {
|
||||||
|
if (Test-Path -LiteralPath $InputData.Destination -PathType Leaf) {
|
||||||
|
$Snapshot.Exists = $true
|
||||||
|
$Snapshot.Length = (Get-Item -LiteralPath $InputData.Destination).Length
|
||||||
|
$Stream = [IO.File]::Open($InputData.Destination,[IO.FileMode]::Open,[IO.FileAccess]::Read,([IO.FileShare]::ReadWrite -bor [IO.FileShare]::Delete))
|
||||||
|
$Prefix = New-Object byte[] 8
|
||||||
|
$Count = $Stream.Read($Prefix,0,$Prefix.Length)
|
||||||
|
if ($Count -gt 0) { $Snapshot.FirstBytes = [BitConverter]::ToString($Prefix,0,$Count) }
|
||||||
|
$Stream.Position = 0
|
||||||
|
$Hash = [Security.Cryptography.SHA256]::Create()
|
||||||
|
$Snapshot.SHA256 = [BitConverter]::ToString($Hash.ComputeHash($Stream)).Replace('-','')
|
||||||
|
$Snapshot.LengthAfterHash = (Get-Item -LiteralPath $InputData.Destination).Length
|
||||||
|
}
|
||||||
|
} catch { $Snapshot.Error = Get-ErrorDetail $_ }
|
||||||
|
finally { if ($Stream) { $Stream.Dispose() }; if ($Hash) { $Hash.Clear() } }
|
||||||
|
return $Snapshot
|
||||||
|
}
|
||||||
|
function Add-BitsSnapshot($Job, $Stage) {
|
||||||
|
$Result.Bits += New-Object PSObject -Property @{
|
||||||
|
Stage=$Stage; TimeUtc=[DateTime]::UtcNow.ToString('o'); JobId=[string]$Job.JobId
|
||||||
|
JobState=[string]$Job.JobState; BytesTotal=$Job.BytesTotal; BytesTransferred=$Job.BytesTransferred
|
||||||
|
FilesTotal=$Job.FilesTotal; FilesTransferred=$Job.FilesTransferred
|
||||||
|
ErrorCode=$Job.ErrorCode; ErrorContext=[string]$Job.ErrorContext; ErrorDescription=$Job.ErrorDescription
|
||||||
|
ProxyUsage=[string]$Job.ProxyUsage; OwnerAccount=$Job.OwnerAccount
|
||||||
|
}
|
||||||
|
Save-Checkpoint
|
||||||
|
}
|
||||||
|
function Remove-OwnedBitsJobs {
|
||||||
|
try {
|
||||||
|
Import-Module BitsTransfer -ErrorAction Stop
|
||||||
|
$Jobs = @(BitsTransfer\Get-BitsTransfer -ErrorAction Stop | Where-Object {
|
||||||
|
$_.DisplayName -eq $InputData.Tag -and $_.Description -eq $Context.RunId
|
||||||
|
})
|
||||||
|
foreach ($Job in $Jobs) {
|
||||||
|
Add-BitsSnapshot $Job 'Before cleanup'
|
||||||
|
BitsTransfer\Remove-BitsTransfer -BitsJob $Job -ErrorAction Stop
|
||||||
|
}
|
||||||
|
} catch { $Result.CleanupErrors += Get-ErrorDetail $_ }
|
||||||
|
}
|
||||||
|
function Add-HttpSnapshot($Response, $Method, $RequestUri) {
|
||||||
|
$Headers = @{}
|
||||||
|
foreach ($Header in @('Content-Length','Content-Range','Content-Type','Transfer-Encoding','Content-Encoding','Accept-Ranges','ETag','Last-Modified','Location','Via','Age','Server')) {
|
||||||
|
$Headers[$Header] = $Response.Headers[$Header]
|
||||||
|
}
|
||||||
|
$Result.Http += New-Object PSObject -Property @{
|
||||||
|
Method=$Method; RequestUri=$RequestUri; ResponseUri=$Response.ResponseUri.AbsoluteUri
|
||||||
|
Status=[int]$Response.StatusCode; ContentLength=$Response.ContentLength; Headers=$Headers
|
||||||
|
}
|
||||||
|
Save-Checkpoint
|
||||||
|
}
|
||||||
|
function Open-HttpResponse($Method, $UseRange) {
|
||||||
|
$CurrentUri = New-Object Uri $Context.URL
|
||||||
|
for ($Redirect = 0; $Redirect -le 10; $Redirect++) {
|
||||||
|
$Request = [Net.HttpWebRequest]::Create($CurrentUri)
|
||||||
|
$Request.Method = $Method
|
||||||
|
$Request.AllowAutoRedirect = $false
|
||||||
|
$Request.Timeout = $Context.TimeoutSeconds * 1000
|
||||||
|
$Request.ReadWriteTimeout = $Context.TimeoutSeconds * 1000
|
||||||
|
if ($UseRange) { $Request.AddRange(0,15) }
|
||||||
|
try { $Response = $Request.GetResponse() }
|
||||||
|
catch {
|
||||||
|
if ($_.Exception.Response) {
|
||||||
|
try { Add-HttpSnapshot $_.Exception.Response $Method $CurrentUri.AbsoluteUri }
|
||||||
|
finally { $_.Exception.Response.Close() }
|
||||||
|
}
|
||||||
|
throw
|
||||||
|
}
|
||||||
|
Add-HttpSnapshot $Response $Method $CurrentUri.AbsoluteUri
|
||||||
|
if (@(301,302,303,307,308) -contains [int]$Response.StatusCode) {
|
||||||
|
try { $NextUri = New-Object Uri $CurrentUri, $Response.Headers['Location'] }
|
||||||
|
finally { $Response.Close() }
|
||||||
|
if (@('http','https') -notcontains $NextUri.Scheme -or $NextUri.UserInfo) { throw 'Unsupported redirect target.' }
|
||||||
|
if ($CurrentUri.Scheme -eq 'https' -and $NextUri.Scheme -eq 'http') { throw 'HTTPS-to-HTTP redirect recorded but not followed.' }
|
||||||
|
$CurrentUri = $NextUri
|
||||||
|
} else { return $Response }
|
||||||
|
}
|
||||||
|
throw 'HTTP redirect limit exceeded.'
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Save-Checkpoint
|
||||||
|
# A child does not inherit this process-local .NET setting. Reproduce the
|
||||||
|
# caller's exact value, rather than selecting a new protocol or changing Windows.
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]$Context.SecurityProtocol
|
||||||
|
if ($InputData.Method -eq 'Cleanup') {
|
||||||
|
Remove-OwnedBitsJobs
|
||||||
|
$Result.Status = 'Complete'
|
||||||
|
} elseif ($InputData.Method -eq 'Environment') {
|
||||||
|
$Info = @{}
|
||||||
|
$Result.Environment = $Info
|
||||||
|
try { $Info['OS'] = Get-WmiObject -Class Win32_OperatingSystem -ErrorAction Stop | Select-Object Caption,Version,BuildNumber,OSArchitecture,ServicePackMajorVersion }
|
||||||
|
catch { $Info['OSError'] = Get-ErrorDetail $_ }
|
||||||
|
Save-Checkpoint
|
||||||
|
$Info['BitsService'] = Get-Service -Name BITS -ErrorAction SilentlyContinue | Select-Object Name,Status,StartType
|
||||||
|
$Info['BitsModules'] = @(Get-Module -ListAvailable -Name BitsTransfer | Select-Object Name,Version,Path)
|
||||||
|
$BitsBinary = Join-Path $Env:WINDIR 'System32\qmgr.dll'
|
||||||
|
$NativeBitsBinary = Join-Path $Env:WINDIR 'Sysnative\qmgr.dll'
|
||||||
|
if (Test-Path -LiteralPath $NativeBitsBinary) { $BitsBinary = $NativeBitsBinary }
|
||||||
|
$Info['BitsBinaryPath'] = $BitsBinary
|
||||||
|
try { $Info['BitsBinaryVersion'] = [Diagnostics.FileVersionInfo]::GetVersionInfo($BitsBinary).FileVersion }
|
||||||
|
catch { $Info['BitsBinaryVersionError'] = Get-ErrorDetail $_ }
|
||||||
|
$Info['DotNetProxyType'] = $Context.ProxyType
|
||||||
|
$Info['CallerProxyAddress'] = $Context.ProxyAddress
|
||||||
|
$Info['DotNetSecurityProtocol'] = [string][Net.ServicePointManager]::SecurityProtocol
|
||||||
|
$Info['CertificateCallbackPresent'] = $Context.CertificateCallbackPresent
|
||||||
|
Save-Checkpoint
|
||||||
|
$Info['WinHttpProxy'] = (& "$Env:WINDIR\System32\netsh.exe" winhttp show proxy 2>&1 | Out-String)
|
||||||
|
$Info['WinHttpRegistryViews'] = @{}
|
||||||
|
foreach ($View in @('32','64')) {
|
||||||
|
# reg.exe supports explicit views without requiring the .NET 4 RegistryView API.
|
||||||
|
try { $Info['WinHttpRegistryViews'][$View] = (& "$Env:WINDIR\System32\reg.exe" query 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp' /v DefaultSecureProtocols ("/reg:" + $View) 2>&1 | Out-String) }
|
||||||
|
catch { $Info['WinHttpRegistryViews'][$View] = $_.Exception.Message }
|
||||||
|
}
|
||||||
|
$Info['Registry'] = @()
|
||||||
|
foreach ($Key in @(
|
||||||
|
'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings',
|
||||||
|
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp',
|
||||||
|
'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp',
|
||||||
|
'HKLM:\SOFTWARE\Microsoft\.NETFramework\v2.0.50727',
|
||||||
|
'HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319',
|
||||||
|
'HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v2.0.50727',
|
||||||
|
'HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319',
|
||||||
|
'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Client',
|
||||||
|
'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Client',
|
||||||
|
'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client'
|
||||||
|
)) {
|
||||||
|
$Values = Get-ItemProperty -LiteralPath $Key -ErrorAction SilentlyContinue
|
||||||
|
$Info['Registry'] += New-Object PSObject -Property @{
|
||||||
|
Key=$Key; Present=($null -ne $Values); Enabled=$Values.Enabled; DisabledByDefault=$Values.DisabledByDefault
|
||||||
|
DefaultSecureProtocols=$Values.DefaultSecureProtocols; SchUseStrongCrypto=$Values.SchUseStrongCrypto
|
||||||
|
SystemDefaultTlsVersions=$Values.SystemDefaultTlsVersions
|
||||||
|
ProxyEnable=$Values.ProxyEnable; ProxyServer=$Values.ProxyServer; AutoConfigURL=$Values.AutoConfigURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Save-Checkpoint
|
||||||
|
$Info['DotNetResolvedProxy'] = $(if ([Net.WebRequest]::DefaultWebProxy) { [Net.WebRequest]::DefaultWebProxy.GetProxy((New-Object Uri $Context.URL)).AbsoluteUri } else { 'None' })
|
||||||
|
$Result.Status = 'Complete'
|
||||||
|
} elseif ($InputData.Method -eq 'Headers') {
|
||||||
|
foreach ($HttpMethod in @('HEAD','Range')) {
|
||||||
|
$Response = $null
|
||||||
|
try {
|
||||||
|
if ($HttpMethod -eq 'HEAD') { $Response = Open-HttpResponse 'HEAD' $false }
|
||||||
|
else { $Response = Open-HttpResponse 'GET' $true }
|
||||||
|
} catch { $Result.Errors += Get-ErrorDetail $_ }
|
||||||
|
finally { if ($Response) { $Response.Close() } }
|
||||||
|
}
|
||||||
|
$Result.Status = $(if ($Result.Errors.Count) { 'Failed' } else { 'Complete' })
|
||||||
|
} else {
|
||||||
|
if ($InputData.ExistingEmpty) { [IO.File]::WriteAllBytes($InputData.Destination,(New-Object byte[] 0)) }
|
||||||
|
$Result.Snapshots += Get-FileSnapshot 'Before transfer'
|
||||||
|
Save-Checkpoint
|
||||||
|
if ($InputData.Method -eq 'Helper') {
|
||||||
|
$Helper = @($Context.Commands | Where-Object { $_.Name -eq 'Download-File' -and $_.Definition })
|
||||||
|
$BitsResolution = $Context.Commands | Where-Object { $_.Name -eq 'Start-BitsTransfer' }
|
||||||
|
if ($Helper.Count -eq 0) {
|
||||||
|
$Result.Status = 'Skipped'
|
||||||
|
$Result.Notes += 'Download-File was not a loaded function; no Tools.ps1 was fetched or sourced.'
|
||||||
|
} elseif ($BitsResolution.CommandType -ne 'Cmdlet' -or $BitsResolution.ModuleName -ne 'BitsTransfer') {
|
||||||
|
$Result.Status = 'Skipped'
|
||||||
|
$Result.Notes += 'Start-BitsTransfer is shadowed or unavailable. Definition recorded; helper not run because its jobs cannot be safely tagged.'
|
||||||
|
} else {
|
||||||
|
Import-Module BitsTransfer -ErrorAction Stop
|
||||||
|
foreach ($Command in $Context.Commands) {
|
||||||
|
if ($Command.Definition -and $Command.Name -ne 'Start-BitsTransfer') {
|
||||||
|
. ([scriptblock]::Create(('function global:{0} {{ {1} }}' -f $Command.Name,$Command.Definition)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$Global:LogFile = $InputData.LogPath
|
||||||
|
# Preserve all native BITS parameters; add only ownership tags so
|
||||||
|
# a timed-out synchronous job can be identified without touching others.
|
||||||
|
$Global:FdNativeBits = Get-Command BitsTransfer\Start-BitsTransfer
|
||||||
|
$Global:FdBitsTag = $InputData.Tag
|
||||||
|
$Global:FdBitsRun = $Context.RunId
|
||||||
|
$Global:FdWorkerResult = $Result
|
||||||
|
function global:Start-BitsTransfer {
|
||||||
|
[CmdletBinding()] param()
|
||||||
|
dynamicparam {
|
||||||
|
$Dictionary = New-Object Management.Automation.RuntimeDefinedParameterDictionary
|
||||||
|
foreach ($Parameter in $Global:FdNativeBits.Parameters.Values) {
|
||||||
|
if (@('Verbose','Debug','ErrorAction','WarningAction','InformationAction','ErrorVariable','WarningVariable','InformationVariable','OutVariable','OutBuffer','PipelineVariable','ProgressAction') -notcontains $Parameter.Name) {
|
||||||
|
$Attributes = New-Object 'Collections.ObjectModel.Collection[Attribute]'
|
||||||
|
foreach ($Attribute in $Parameter.Attributes) { $Attributes.Add($Attribute) }
|
||||||
|
$Dynamic = New-Object Management.Automation.RuntimeDefinedParameter $Parameter.Name,$Parameter.ParameterType,$Attributes
|
||||||
|
$Dictionary.Add($Parameter.Name,$Dynamic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $Dictionary
|
||||||
|
}
|
||||||
|
process {
|
||||||
|
$Global:FdWorkerResult.HelperBitsSource += @($PSBoundParameters['Source'])
|
||||||
|
$PSBoundParameters['DisplayName'] = $Global:FdBitsTag
|
||||||
|
$PSBoundParameters['Description'] = $Global:FdBitsRun
|
||||||
|
& $Global:FdNativeBits @PSBoundParameters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$Result.Notes += 'Loaded helper functions copied into a fresh process; BITS display name/description tagged for cleanup; helper logs redirected to diagnostic scratch.'
|
||||||
|
$Result.HelperOutput = @(Download-File -URL $Context.URL -File $InputData.Destination -ErrorAction Stop 2>&1 | ForEach-Object { [string]$_ })
|
||||||
|
}
|
||||||
|
} elseif ($InputData.Method -eq 'BitsSync') {
|
||||||
|
Import-Module BitsTransfer -ErrorAction Stop
|
||||||
|
BitsTransfer\Start-BitsTransfer -Source $Context.URL -Destination $InputData.Destination -DisplayName $InputData.Tag -Description $Context.RunId -ErrorAction Stop
|
||||||
|
} elseif ($InputData.Method -eq 'BitsJob') {
|
||||||
|
Import-Module BitsTransfer -ErrorAction Stop
|
||||||
|
$Job = BitsTransfer\Start-BitsTransfer -Source $Context.URL -Destination $InputData.Destination -DisplayName $InputData.Tag -Description $Context.RunId -Asynchronous -ErrorAction Stop
|
||||||
|
$PreviousState = ''
|
||||||
|
while ($true) {
|
||||||
|
$Job = BitsTransfer\Get-BitsTransfer -JobId $Job.JobId -ErrorAction Stop
|
||||||
|
if ([string]$Job.JobState -ne $PreviousState) { Add-BitsSnapshot $Job 'State change'; $PreviousState = [string]$Job.JobState }
|
||||||
|
if (@('Transferred','Error','TransientError','Suspended','Cancelled','Acknowledged') -contains [string]$Job.JobState) { break }
|
||||||
|
Start-Sleep -Milliseconds 250
|
||||||
|
}
|
||||||
|
if ([string]$Job.JobState -ne 'Transferred') { throw ('BITS job ended probe in {0}: {1}' -f $Job.JobState,$Job.ErrorDescription) }
|
||||||
|
Add-BitsSnapshot $Job 'Before completion'
|
||||||
|
BitsTransfer\Complete-BitsTransfer -BitsJob $Job -ErrorAction Stop
|
||||||
|
} elseif ($InputData.Method -eq 'DotNetGet') {
|
||||||
|
$Response = $null; $InputStream = $null; $OutputStream = $null
|
||||||
|
try {
|
||||||
|
$Response = Open-HttpResponse 'GET' $false
|
||||||
|
$InputStream = $Response.GetResponseStream()
|
||||||
|
$OutputStream = [IO.File]::Open($InputData.Destination,[IO.FileMode]::Create,[IO.FileAccess]::Write,[IO.FileShare]::Read)
|
||||||
|
$Buffer = New-Object byte[] 65536
|
||||||
|
$Received = [long]0
|
||||||
|
while (($Count = $InputStream.Read($Buffer,0,$Buffer.Length)) -gt 0) { $OutputStream.Write($Buffer,0,$Count); $Received += $Count }
|
||||||
|
if ($Response.ContentLength -ge 0 -and $Received -ne $Response.ContentLength) { throw "HTTP body length mismatch: expected $($Response.ContentLength), received $Received." }
|
||||||
|
} finally {
|
||||||
|
if ($OutputStream) { $OutputStream.Dispose() }
|
||||||
|
if ($InputStream) { $InputStream.Dispose() }
|
||||||
|
if ($Response) { $Response.Close() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($Result.Status -ne 'Skipped') {
|
||||||
|
$Result.Snapshots += Get-FileSnapshot 'After transfer'
|
||||||
|
Save-Checkpoint
|
||||||
|
Start-Sleep -Milliseconds 1000
|
||||||
|
$Result.Snapshots += Get-FileSnapshot 'One second later'
|
||||||
|
$After = $Result.Snapshots[$Result.Snapshots.Count-1]
|
||||||
|
$Result.Status = $(if ($After.Error) { 'Failed' } elseif (!$After.Exists) { 'MissingFile' } elseif ($After.Length -eq 0) { 'EmptyFile' } else { 'Complete' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
$Result.Status = 'Failed'
|
||||||
|
$Result.Errors += Get-ErrorDetail $_
|
||||||
|
if ($InputData.Destination) { $Result.Snapshots += Get-FileSnapshot 'After error' }
|
||||||
|
} finally {
|
||||||
|
Save-Checkpoint
|
||||||
|
if (@('Helper','BitsSync','BitsJob') -contains $InputData.Method) { Remove-OwnedBitsJobs }
|
||||||
|
Save-Checkpoint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$FdWorkerPath = Join-Path $FdWork 'worker.ps1'
|
||||||
|
[IO.File]::WriteAllText($FdWorkerPath,$FdWorker.ToString(),[Text.Encoding]::UTF8)
|
||||||
|
|
||||||
|
# Capture both child pipes concurrently without PowerShell callbacks on foreign
|
||||||
|
# threads. .NET file APIs also avoid Start-Process's wildcard path expansion.
|
||||||
|
if (!('EmberkomDownloadDiagnostic.ChildRunner' -as [type])) {
|
||||||
|
Add-Type -TypeDefinition @'
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
namespace EmberkomDownloadDiagnostic {
|
||||||
|
public class ChildResult { public bool TimedOut; public int ExitCode; }
|
||||||
|
public static class ChildRunner {
|
||||||
|
public static ChildResult Run(string exe,string args,string directory,string stdout,string stderr,int milliseconds) {
|
||||||
|
using (StreamWriter output=new StreamWriter(stdout))
|
||||||
|
using (StreamWriter error=new StreamWriter(stderr))
|
||||||
|
using (Process child=new Process()) {
|
||||||
|
child.StartInfo.FileName=exe; child.StartInfo.Arguments=args;
|
||||||
|
child.StartInfo.WorkingDirectory=directory;
|
||||||
|
child.StartInfo.UseShellExecute=false; child.StartInfo.CreateNoWindow=true;
|
||||||
|
child.StartInfo.WindowStyle=ProcessWindowStyle.Hidden;
|
||||||
|
child.StartInfo.RedirectStandardOutput=true; child.StartInfo.RedirectStandardError=true;
|
||||||
|
child.OutputDataReceived+=delegate(object sender,DataReceivedEventArgs e) { if(e.Data!=null) lock(output) { output.WriteLine(e.Data); } };
|
||||||
|
child.ErrorDataReceived+=delegate(object sender,DataReceivedEventArgs e) { if(e.Data!=null) lock(error) { error.WriteLine(e.Data); } };
|
||||||
|
child.Start(); child.BeginOutputReadLine(); child.BeginErrorReadLine();
|
||||||
|
ChildResult result=new ChildResult();
|
||||||
|
result.TimedOut=!child.WaitForExit(milliseconds);
|
||||||
|
if(result.TimedOut) {
|
||||||
|
try { child.Kill(); } catch(InvalidOperationException) { }
|
||||||
|
if(!child.WaitForExit(5000)) throw new TimeoutException("Diagnostic child did not exit after termination.");
|
||||||
|
}
|
||||||
|
child.WaitForExit(); // Process has exited; drain the asynchronous pipe callbacks.
|
||||||
|
result.ExitCode=child.ExitCode;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'@
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-FdWorker($Spec, $Limit) {
|
||||||
|
$InputFile = Join-Path $FdWork ($Spec.Name + '-input.clixml')
|
||||||
|
Export-FdXml $Spec $InputFile 8
|
||||||
|
$Code = '& ([scriptblock]::Create([IO.File]::ReadAllText(''' + $FdWorkerPath.Replace("'","''") + '''))) ''' + $InputFile.Replace("'","''") + ''''
|
||||||
|
$Encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($Code))
|
||||||
|
$Arguments = '-NoProfile -NonInteractive '
|
||||||
|
if ($PSVersionTable.PSVersion.Major -eq 2) { $Arguments += '-Version 2.0 ' }
|
||||||
|
$Arguments += '-EncodedCommand ' + $Encoded
|
||||||
|
$Child = [EmberkomDownloadDiagnostic.ChildRunner]::Run($FdExe,$Arguments,$FdContext.WorkingDirectory,(Join-Path $FdWork ($Spec.Name + '-stdout.txt')),(Join-Path $FdWork ($Spec.Name + '-stderr.txt')),($Limit * 1000))
|
||||||
|
$TimedOut = $Child.TimedOut
|
||||||
|
$ExitCode = $Child.ExitCode
|
||||||
|
$Item = $null
|
||||||
|
if (Test-Path -LiteralPath $Spec.ResultPath) {
|
||||||
|
try { $Item = Import-Clixml -Path ([Management.Automation.WildcardPattern]::Escape($Spec.ResultPath)) } catch { $Item = $null }
|
||||||
|
}
|
||||||
|
if (!$Item) { $Item = New-Object PSObject -Property @{Name=$Spec.Name; Method=$Spec.Method; ExistingEmpty=$Spec.ExistingEmpty; Status='Failed'; Notes=@('Child did not produce a readable result.'); Errors=@(); Snapshots=@(); CleanupErrors=@(); ElapsedSeconds=$Limit} }
|
||||||
|
if ($TimedOut) { $Item.Status = 'TimedOut'; $Item.Notes += "Stopped after $Limit seconds; partial checkpoint retained." }
|
||||||
|
elseif (($null -ne $ExitCode -and $ExitCode -ne 0) -or $Item.Status -eq 'Running') { $Item.Status = 'Failed' }
|
||||||
|
foreach ($StreamName in @('stdout','stderr')) {
|
||||||
|
$StreamPath = Join-Path $FdWork ($Spec.Name + '-' + $StreamName + '.txt')
|
||||||
|
$StreamText = $(if (Test-Path -LiteralPath $StreamPath) { [IO.File]::ReadAllText($StreamPath) } else { '' })
|
||||||
|
Add-Member -InputObject $Item -MemberType NoteProperty -Name $StreamName -Value $StreamText
|
||||||
|
}
|
||||||
|
Add-Member -InputObject $Item -MemberType NoteProperty -Name ChildExitCode -Value $ExitCode
|
||||||
|
return $Item
|
||||||
|
}
|
||||||
|
|
||||||
|
$FdReport = New-Object PSObject -Property @{
|
||||||
|
SchemaVersion=1; RunId=$FdRunId; StartedUtc=[DateTime]::UtcNow.ToString('o'); Context=$FdContext
|
||||||
|
Probes=@(); Cleanup=@(); Findings=@(); ReportDirectory=$FdRoot
|
||||||
|
Limitations=@(
|
||||||
|
'Fresh child processes reproduce the caller TLS selection but not existing pooled connections, custom certificate callbacks, or custom in-memory proxy objects.',
|
||||||
|
'Registry paths reflect this process registry view; a 32-bit process may not expose the native 64-bit view.',
|
||||||
|
'Helper dependencies outside the captured functions/global LogFile are not reproduced.',
|
||||||
|
'Scratch files preserve the original URL extension when usable, but not the production path; path-specific security behavior may differ.',
|
||||||
|
'PowerShell 2-4 compatibility requires actual runtime validation; newer features elsewhere in Tools.ps1 are separate from this diagnostic.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
$FdReportXml = Join-Path $FdRoot 'report.clixml'
|
||||||
|
$FdReportText = Join-Path $FdRoot 'report.txt'
|
||||||
|
try {
|
||||||
|
$FdCases = @('Environment','Headers','Helper','BitsSync','BitsJob','DotNetGet')
|
||||||
|
foreach ($FdMethod in $FdCases) {
|
||||||
|
$FdVariants = @($false)
|
||||||
|
if (@('Helper','BitsSync','BitsJob','DotNetGet') -contains $FdMethod) { $FdVariants = @($false,$true) }
|
||||||
|
foreach ($FdExisting in $FdVariants) {
|
||||||
|
$FdName = $FdMethod + $(if ($FdExisting) { '-existing-empty' } else { '-new' })
|
||||||
|
$FdSpec = New-Object PSObject -Property @{
|
||||||
|
Name=$FdName; Method=$FdMethod; ExistingEmpty=$FdExisting; ContextPath=$FdContextPath
|
||||||
|
ResultPath=(Join-Path $FdWork ($FdName + '-result.clixml'))
|
||||||
|
Destination=(Join-Path $FdWork ($FdName + $FdExtension))
|
||||||
|
LogPath=(Join-Path $FdWork ($FdName + '-helper.log'))
|
||||||
|
Tag=('Emberkom-DownloadDiagnostic-' + $FdRunId + '-' + $FdName)
|
||||||
|
}
|
||||||
|
Write-Host ("Testing {0} (limit {1}s)..." -f $FdName,$FdTimeout)
|
||||||
|
$FdResult = Invoke-FdWorker $FdSpec $FdTimeout
|
||||||
|
if (Test-Path -LiteralPath $FdSpec.LogPath) { Add-Member -InputObject $FdResult -MemberType NoteProperty -Name HelperLog -Value ([IO.File]::ReadAllText($FdSpec.LogPath)) }
|
||||||
|
$FdReport.Probes += $FdResult
|
||||||
|
Export-FdXml $FdReport $FdReportXml 20
|
||||||
|
# A killed child cannot run finally. Always use a separate, bounded
|
||||||
|
# cleanup worker; filter by BOTH unpredictable run ID and exact tag.
|
||||||
|
if (@('Helper','BitsSync','BitsJob') -contains $FdMethod) {
|
||||||
|
$FdSpec.Method = 'Cleanup'
|
||||||
|
$FdSpec.Name += '-cleanup'
|
||||||
|
$FdSpec.ResultPath = Join-Path $FdWork ($FdSpec.Name + '-result.clixml')
|
||||||
|
$FdReport.Cleanup += Invoke-FdWorker $FdSpec 15
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$FdDownloads = @($FdReport.Probes | Where-Object { @('Helper','BitsSync','BitsJob','DotNetGet') -contains $_.Method -and $_.Status -ne 'Skipped' })
|
||||||
|
$FdBad = @($FdDownloads | Where-Object { $_.Status -ne 'Complete' })
|
||||||
|
$FdHashes = @($FdDownloads | Where-Object { $_.Status -eq 'Complete' } | ForEach-Object { $_.Snapshots[$_.Snapshots.Count-1].SHA256 } | Sort-Object -Unique)
|
||||||
|
if ($FdBad.Count -eq 0 -and $FdHashes.Count -eq 1) {
|
||||||
|
$FdReport.Findings += 'All tested download paths produced the same nonempty file. Failure not reproduced; no transport replacement is justified by this run.'
|
||||||
|
} else {
|
||||||
|
$FdReport.Findings += 'A failure or content difference was observed. Compare per-probe errors, HTTP responses, BITS byte counts, and file snapshots before choosing a shared fix.'
|
||||||
|
}
|
||||||
|
foreach ($FdProbe in $FdDownloads) {
|
||||||
|
$FdAfter = @($FdProbe.Snapshots | Where-Object { $_.Label -eq 'After transfer' -or $_.Label -eq 'One second later' })
|
||||||
|
if ($FdAfter.Count -eq 2 -and ($FdAfter[0].SHA256 -ne $FdAfter[1].SHA256 -or $FdAfter[0].Length -ne $FdAfter[1].Length)) {
|
||||||
|
$FdReport.Findings += "File changed after transfer in $($FdProbe.Name); investigate post-download modification."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
# This directory is created exclusively by this invocation. Delete only its
|
||||||
|
# immediate scratch files; never recurse or touch the production destination.
|
||||||
|
$FdResolvedWork = (Resolve-Path -LiteralPath $FdWork).Path
|
||||||
|
$FdExpectedWork = [IO.Path]::GetFullPath((Join-Path $FdRoot 'work'))
|
||||||
|
if ($FdResolvedWork -ne $FdExpectedWork -or (Get-Item -LiteralPath $FdResolvedWork).Attributes -band [IO.FileAttributes]::ReparsePoint) {
|
||||||
|
$FdReport.Findings += 'Scratch path validation failed; cleanup skipped.'
|
||||||
|
} else {
|
||||||
|
foreach ($FdScratch in @(Get-ChildItem -LiteralPath $FdResolvedWork -Force)) {
|
||||||
|
if (!$FdScratch.PSIsContainer) {
|
||||||
|
try { Remove-Item -LiteralPath $FdScratch.FullName -Force -ErrorAction Stop }
|
||||||
|
catch { $FdReport.Findings += ('Could not remove scratch file: ' + $FdScratch.FullName + ': ' + $_.Exception.Message) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (@(Get-ChildItem -LiteralPath $FdResolvedWork -Force).Count -eq 0) { Remove-Item -LiteralPath $FdResolvedWork -Force }
|
||||||
|
}
|
||||||
|
Export-FdXml $FdReport $FdReportXml 20
|
||||||
|
# A readable recursive rendering retains nested exception and HTTP details.
|
||||||
|
function Format-FdReport($Value, $Indent) {
|
||||||
|
if ($null -eq $Value) { return ($Indent + '<null>') }
|
||||||
|
if ($Value -is [string] -or $Value -is [ValueType]) { return ($Indent + [string]$Value) }
|
||||||
|
if ($Value -is [Collections.IDictionary]) {
|
||||||
|
foreach ($Key in @($Value.Keys | Sort-Object)) { $Indent + [string]$Key + ':'; Format-FdReport $Value[$Key] ($Indent + ' ') }
|
||||||
|
} elseif ($Value -is [Collections.IEnumerable]) {
|
||||||
|
foreach ($Entry in $Value) { Format-FdReport $Entry ($Indent + ' ') }
|
||||||
|
} else {
|
||||||
|
foreach ($Property in $Value.PSObject.Properties) {
|
||||||
|
if (@('NoteProperty','Property') -contains [string]$Property.MemberType) { $Indent + $Property.Name + ':'; Format-FdReport $Property.Value ($Indent + ' ') }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$FdText = @(Format-FdReport $FdReport '') -join [Environment]::NewLine
|
||||||
|
[IO.File]::WriteAllText($FdReportText,$FdText,[Text.Encoding]::UTF8)
|
||||||
|
}
|
||||||
|
Write-Host ("Diagnostic reports: {0}" -f $FdRoot)
|
||||||
|
New-Object PSObject -Property @{TextReport=$FdReportText; XmlReport=$FdReportXml; Findings=$FdReport.Findings}
|
||||||
|
} $URL $OutputDirectory $TimeoutSeconds
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
## Install and import additional Powershell modules
|
# Install and import additional Powershell modules
|
||||||
Install-MSP360Module
|
Import-MSP360Module
|
||||||
Import-Module -Name MSP360
|
|
||||||
|
|
||||||
## Uninstall the agent
|
# Uninstall the agent
|
||||||
Write-Output "Uninstalling Emberkom Cloud Backup agent"
|
Write-Output "Uninstalling Emberkom Cloud Backup agent"
|
||||||
Try { Remove-MBSAgent -RemoveSettings -Force }
|
Try { Remove-MBSAgent -RemoveSettings -Force }
|
||||||
Catch { Write-Error $_.Exception.Message }
|
Catch { Write-Error $_.Exception.Message }
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ Else
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Kill all running instances of TeamViewer
|
# Kill all running instances of TeamViewer
|
||||||
Get-Process | Where { $_.Name -like "*TeamViewer*" } | Stop-Process -Force
|
Get-Process | Where-Object { $_.Name -like "*TeamViewer*" } | Stop-Process -Force
|
||||||
|
|
||||||
If ( Test-Path "${INSTALLDIR}\uninstall.exe" )
|
If ( Test-Path "${INSTALLDIR}\uninstall.exe" )
|
||||||
{ Start-Process "${INSTALLDIR}\uninstall.exe" -ArgumentList "/S" -Wait }
|
{ Start-Process "${INSTALLDIR}\uninstall.exe" -ArgumentList "/S" -Wait }
|
||||||
|
|||||||
@@ -57,21 +57,50 @@ If ( IsWindowsBuild -ge 9200 )
|
|||||||
$PackageFullName = $($(Get-AppxPackage $App).PackageFullName)
|
$PackageFullName = $($(Get-AppxPackage $App).PackageFullName)
|
||||||
$ProPackageFullName = $($(Get-AppxProvisionedPackage -Online | Where-Object {$_.Displayname -eq $App}).PackageName)
|
$ProPackageFullName = $($(Get-AppxProvisionedPackage -Online | Where-Object {$_.Displayname -eq $App}).PackageName)
|
||||||
|
|
||||||
# If the package exists, uninstall it
|
# Make sure we have a package
|
||||||
If ($PackageFullName) {
|
If ($PackageFullName) {
|
||||||
Write-Output “Removing package: ${PackageFullName}”
|
|
||||||
Try { Remove-AppxPackage -Package $PackageFullName }
|
# Test if the package is an array
|
||||||
Catch { Write-Error $_.Exception.Message }
|
If ( $PackageFullName -is [System.Array] ) {
|
||||||
|
|
||||||
|
# Iterate through the array of packages and uninstall each one
|
||||||
|
ForEach ( $package in $PackageFullName ) {
|
||||||
|
Write-Output “Removing package: ${package}”
|
||||||
|
Try { Remove-AppxPackage -Package $package }
|
||||||
|
Catch { Write-Error $_.Exception.Message }
|
||||||
|
}
|
||||||
|
|
||||||
|
# If it's not an array, just uninstall the package
|
||||||
|
} Else {
|
||||||
|
Write-Output “Removing package: ${PackageFullName}”
|
||||||
|
Try { Remove-AppxPackage -Package $PackageFullName }
|
||||||
|
Catch { Write-Error $_.Exception.Message }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# If the provisioned package exists, uninstall it
|
# Make sure we have a provisioned package
|
||||||
If ($ProPackageFullName) {
|
If ($ProPackageFullName) {
|
||||||
Write-Output “Removing provisioned package: ${ProPackageFullName}”
|
|
||||||
Try { Remove-AppxProvisionedPackage -PackageName $ProPackageFullName -Online -AllUsers }
|
# Test if the provisioned package is an array
|
||||||
Catch { Write-Error $_.Exception.Message }
|
If ( $ProPackageFullName -is [System.Array] ) {
|
||||||
|
|
||||||
|
# Iterate through the array of provisioned packages and uninstall each one
|
||||||
|
ForEach ( $propackage in $ProPackageFullName ) {
|
||||||
|
Write-Output “Removing provisioned package: ${propackage}”
|
||||||
|
Try { Remove-AppxProvisionedPackage -PackageName $propackage -Online -AllUsers }
|
||||||
|
Catch { Write-Error $_.Exception.Message }
|
||||||
|
}
|
||||||
|
|
||||||
|
# If it's not an array, just uninstall the provisioned package
|
||||||
|
} Else {
|
||||||
|
Write-Output “Removing provisioned package: ${ProPackageFullName}”
|
||||||
|
Try { Remove-AppxProvisionedPackage -PackageName $ProPackageFullName -Online -AllUsers }
|
||||||
|
Catch { Write-Error $_.Exception.Message }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} Else { Write-Output "Cannot remove Appx packages because this endpoint is not running Windows 8 or higher" }
|
} Else { Write-Output "Cannot remove Appx packages because this endpoint is not running Windows 8 or higher" }
|
||||||
|
|
||||||
# Uninstall unwanted Dell apps
|
# Uninstall unwanted Dell apps
|
||||||
'Dell*SupportAssist*', 'Dell Digital Delivery*' | Uninstall-MSI
|
'Dell SupportAssist*', 'Dell Digital Delivery*', 'Dell Optimizer*', 'Dell Peripheral*', 'Dell Power Manager*', 'DellOptimizerUI*' | Uninstall-MSI
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
"wireguard" | Uninstall-MSI -Match
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
If ( $LFCLI ) {
|
|
||||||
|
|
||||||
## Create the path to $FileToUpload and zip the specified directory if necessary
|
|
||||||
If ( $(Get-Item $DataToUpload) -is [System.IO.DirectoryInfo] ) {
|
|
||||||
$FileToUpload = '{0}\{1}.zip' -f $Global:OutputDirectory,$(Split-Path -Leaf $DataToUpload)
|
|
||||||
Zip-Object -path $DataToUpload -dest $FileToUpload
|
|
||||||
$DeleteAfterUpload = $true
|
|
||||||
}
|
|
||||||
Else {
|
|
||||||
$FileToUpload = $DataToUpload
|
|
||||||
$DeleteAfterUpload = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
Install-PSAteraModule
|
|
||||||
Import-Module -Name PSAtera
|
|
||||||
Set-AteraAPIKey -APIKey $AteraAPIKey
|
|
||||||
$LFHost = $(Get-AteraCustomValue -ObjectType Customer -ObjectId 9 -FieldName 'LiquidFiles Host URL').ValueAsString
|
|
||||||
$FiledropURL = "${LFHost}/filedrop/cmd"
|
|
||||||
|
|
||||||
## Set the message properties for the file upload
|
|
||||||
$From = '{0}@{1}.net' -f $Env:COMPUTERNAME,$($Global:MSPName -replace " ","").ToLower()
|
|
||||||
$Subject = [System.IO.Path]::GetFileNameWithoutExtension($FileToUpload)
|
|
||||||
$Message = ""
|
|
||||||
|
|
||||||
If ( Test-Path $FileToUpload ) {
|
|
||||||
Write-Output "Uploading ""${FileToUpload}"""
|
|
||||||
|
|
||||||
If ( $DeleteAfterUpload ) {
|
|
||||||
Try { Start-Process "${LFCLI}" -ArgumentList "filedrop /url:${FiledropURL} /from:""${From}"" /subject:""${Subject}"" /msg:""${Message}"" /c:n /deleteAfterUpload /f:""${FileToUpload}""" -Wait }
|
|
||||||
Catch { Write-Error $_.Exception.Message }
|
|
||||||
}
|
|
||||||
Else {
|
|
||||||
Try { Start-Process "${LFCLI}" -ArgumentList "filedrop /url:${FiledropURL} /from:""${From}"" /subject:""${Subject}"" /msg:""${Message}"" /c:n /f:""${FileToUpload}""" -Wait }
|
|
||||||
Catch { Write-Error $_.Exception.Message }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Else { Write-Output "The specified file does not exist: ""${FileToUpload}""" }
|
|
||||||
}
|
|
||||||
Else { Write-Output "Could not upload any data because the LiquidFiles CLI tool was not found" }
|
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# File download diagnosis
|
||||||
|
|
||||||
|
`Test-FileDownload.ps1` compares the loaded `Download-File` helper, synchronous BITS, an inspectable BITS job, and a direct .NET GET. It makes no changes to `Tools.ps1`, collector behavior, Windows TLS configuration, or proxy configuration. Downloaded files are never executed.
|
||||||
|
|
||||||
|
## Run on the affected host
|
||||||
|
|
||||||
|
Use the same RMM account, PowerShell executable, and bootstrap as the failing job. Keep the existing Tools bootstrap unchanged and replace the final collector invocation with this block after publishing the diagnostic script to the repository:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$DiagnosticClient = New-Object Net.WebClient
|
||||||
|
try {
|
||||||
|
$DiagnosticText = $DiagnosticClient.DownloadString(
|
||||||
|
'https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Test-FileDownload.ps1'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
finally { $DiagnosticClient.Dispose() }
|
||||||
|
|
||||||
|
& ([scriptblock]::Create($DiagnosticText)) `
|
||||||
|
-URL 'https://xfer.emberkom.com/shares/tools/folders/9b9988fb-c53a-4833-a4ad-68d01a3021bb/files/dsa-collect-windows-amd64-exe/download/dsa-collect-windows-amd64.exe' `
|
||||||
|
-OutputDirectory "$Env:ProgramData\Emberkom\Output" `
|
||||||
|
-TimeoutSeconds 120
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not invoke it through `Download-File`, since that is the function being investigated. Alternatively, save the diagnostic on the affected host and call it directly with `& 'C:\path\Test-FileDownload.ps1' -URL '<download URL>'`. If Tools cannot load on a legacy engine, a standalone run still tests the other methods and records that the helper was unavailable.
|
||||||
|
|
||||||
|
Use `powershell.exe`, not ISE or an embedded PowerShell host. The diagnostic rejects other executables instead of guessing how to launch equivalent child processes.
|
||||||
|
|
||||||
|
The mandatory `-URL` can be any HTTP(S) file URL without embedded credentials. `-OutputDirectory` defaults to the account's temporary directory. Each invocation creates its own `file-download-diagnostic-<GUID>` subdirectory, containing `report.txt` and `report.clixml`. No reports are uploaded automatically. Retrieve `report.txt` from the affected host for analysis; the CLIXML file preserves structured details.
|
||||||
|
|
||||||
|
There are ten sequential probes: environment, HTTP headers/ranges, and two destination variants for each of four download paths. Eight probes download the payload, so use a small representative file. Each probe has a 120-second default wall-clock limit, followed by a separate cleanup attempt of at most 15 seconds for BITS-related probes. Allow about 22 minutes plus process startup/reporting overhead for the worst case, or specify a shorter timeout. Large downloads and hashing count toward the timeout.
|
||||||
|
|
||||||
|
## What the report captures
|
||||||
|
|
||||||
|
- OS, CLR and PowerShell versions, process bitness, executable, identity, BITS service/module information, and proxy/TLS settings. Missing registry values are recorded rather than invented as defaults.
|
||||||
|
- Definitions and SHA-256 hashes of the loaded helper functions, plus command resolution. The full Tools script is not executed by the diagnostic.
|
||||||
|
- HTTP redirect chains, status, content length, range support, and selected response headers. Cookies and authorization headers are not collected.
|
||||||
|
- Independent downloads to new destinations and existing empty destinations, byte counts, hashes, first bytes, timing, and full exceptions.
|
||||||
|
- BITS job state changes and byte counts before completion, and file snapshots immediately after transfer and one second later.
|
||||||
|
|
||||||
|
Child processes use the caller's PowerShell executable and account, with its .NET TLS selection reproduced in the child only. Existing pooled connections, custom in-memory proxy objects, and certificate callbacks are not copied. The helper probe copies the captured functions, redirects helper logging into diagnostic scratch, and adds ownership tags to BITS jobs. A shadowed/non-native `Start-BitsTransfer` is recorded and the helper probe is skipped rather than risking cleanup of unidentified jobs.
|
||||||
|
|
||||||
|
Scratch filenames retain the original URL's extension when usable, but the production destination is never touched. Path-specific security rules may therefore behave differently. The loaded helper's logging overrides are copied only into its child process.
|
||||||
|
|
||||||
|
Only jobs matching both the diagnostic's unique run ID and exact probe tag are removed. Scratch cleanup does not recurse or touch the production executable. Cleanup errors and unfinished probes remain visible in the report. Reports include local machine/account details and loaded source definitions; review them before sharing.
|
||||||
|
|
||||||
|
The code uses PowerShell 2 syntax and APIs available to legacy .NET, but local validation on PowerShell 5.1 does not prove execution on 2, 3, or 4. Run the diagnostic on actual legacy runtimes before declaring them supported. Full `Tools.ps1` loadability is a separate issue: it already contains newer syntax and commands, including `-in`/`-notin`, `::new()`, `ConvertFrom-Json`, and `Get-FileHash` outside the downloader.
|
||||||
|
|
||||||
|
## Interpret the comparison
|
||||||
|
|
||||||
|
| Observation | Next investigation |
|
||||||
|
|---|---|
|
||||||
|
| Only the helper fails | Compare `HelperBitsSource` with the original URL, the redirect chain, and URL-resolution requests. |
|
||||||
|
| BITS fails and direct GET succeeds | Compare service identity, WinHTTP/.NET proxy and TLS settings, HTTP behavior, and BITS error codes. |
|
||||||
|
| Both transports fail | Investigate endpoint responses, certificate trust, connectivity, and the affected machine's configuration. |
|
||||||
|
| BITS reports bytes transferred but the resulting file differs | Inspect completion errors, destination access, and post-download changes. |
|
||||||
|
| All paths produce the same nonempty hash | Failure was not reproduced. This run does not justify changing the shared transport. |
|
||||||
|
|
||||||
|
Neither a positive byte count nor matching hashes authenticates a publisher's executable. These are diagnostic comparisons, not a replacement for publisher-supplied integrity information.
|
||||||
|
|
||||||
|
## Shared caller audit
|
||||||
|
|
||||||
|
The audit found 24 active call sites across 20 files: 19 scripts plus five wrapper call sites in `Tools.ps1`. Commented-out calls and tests are excluded.
|
||||||
|
|
||||||
|
| Callers | Required behavior |
|
||||||
|
|---|---|
|
||||||
|
| `Run-Script`, `Source-PSScript`, `Uninstall-MicrosoftOffice` | Return one completed script path with its extension; allow immediate sourcing/execution. |
|
||||||
|
| `Install-MSI`, `Install-4KVideoDownloader`, `Install-LiquidFilesOutlookAgent`, `Install-SimpleInOut`, `Install-SpecsIntact`, `Install-Wireguard` | Return a completed installer path for immediate MSI invocation and later cleanup. |
|
||||||
|
| `Install-Nextcloud`, `Install-OpenVPN`, `Install-VLC` | Work inside a subexpression or via positional URL; return only the downloaded path. |
|
||||||
|
| `Install-AutodeskDesktopConnector`, `Install-Enscape`, `Install-ESETManagementAgent`, `Install-MicrosoftOffice365`, `Install-SketchUp` | Honor an explicit destination; handle redirects and potentially large installers; finish before installation. |
|
||||||
|
| `Fix-AutodeskProductLicensing`, `Test-InternetBandwidth`, `Get-LiquidFilesCLI` | Retain a usable ZIP filename and extension for extraction and derived working-directory names. |
|
||||||
|
| `Get-DSAManifest`, `Fix-UpdateLocalHosts`, `Remove-AllESETProducts`, `Install-LocalTools` | Honor fixed executable paths and complete replacement before execution. Caller-level caching remains separate. |
|
||||||
|
|
||||||
|
All concrete download URLs in this audit use HTTPS. The public wrappers also accept caller-provided HTTP(S) URLs. No audited caller consumes a BITS job object or explicitly controls background transfers/resume.
|
||||||
|
|
||||||
|
`Download-File` currently calls `Get-AbsoluteURI`, which first calls `IsURLValid`; both issue GET requests without disposing their responses. The final response URI is then passed to BITS. This is an observed implementation detail to investigate, not proof that BITS should be replaced.
|
||||||
|
|
||||||
|
Any subsequent shared fix must preserve URL binding, explicit/automatic filenames, synchronous completion, one success path on the output stream, redirects, large-file streaming, shell architectures, and `Run-Script` shared scope. It must also address validation and failure propagation before callers install or extract content. Production implementation and rollout follow the diagnostic findings; this change adds no fallback or collector-specific transport option.
|
||||||
|
|
||||||
|
## Local verification
|
||||||
|
|
||||||
|
Run `tests\Test-FileDownload.Tests.ps1` in both System32 and SysWOW64 Windows PowerShell. The suite serves synthetic payloads over loopback, tests both destination variants, redirects, chunked/empty/truncated/error responses, timeouts, hashing, and preservation of an unrelated BITS job. It requires access to the BITS service but performs no external downloads, installations, or uploads.
|
||||||
|
|
||||||
|
The affected RMM host and actual PowerShell 2-4 engines must still be tested separately. No local result substitutes for those environments.
|
||||||
|
|
||||||
|
Local verification on 2026-09-30 passed under both 32-bit and 64-bit PowerShell 5.1. A live 32-bit run against the AMD64 collector URL returned 3,455,488 bytes with SHA-256 `CC693EF2FEEEF05C99410B6F7A05AE2621EA0B89BA6D3E12C50B7F059D557F99` for every download method and destination variant. This is an observed comparison hash, not a publisher-provided trust anchor. The failure was not reproduced, so the current recommendation is to retain the shared transport until the affected RMM run supplies evidence.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
|
||||||
|
# $CleanupPath and $DaysWithNoModification must be set by caller
|
||||||
|
|
||||||
|
Run-Script -LivePSScript Cleanup-RevitTempFilesOnFileServerCalcOnly
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Cleanup-SystemTempFiles.ps1')))
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Create-BatteryReport
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
|
||||||
|
# Create the Enberkom Administrator user
|
||||||
|
New-LocalAccount -Username 'ek-admin' -Password 'C0d3R3d@!!' -FullName 'Emberkom Administrator' -Description 'Used by Emberkom to administer this endpoint' -MakeAdmin -Hide
|
||||||
|
|
||||||
|
# Delete the provisioning user that may have been created via PPKG
|
||||||
|
If ( $(Get-LocalUser -Name "ek-admin") ) {
|
||||||
|
Run-Script -LivePSScript Remove-ProvisioningAdminUser
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Create-NewTicketFromAppCrash-KERNELBASEdll
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Create-NewTicketFromBSODAnalysis
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
$VisionURL = 'https://dbia.deltekfirst.com/DBIAClient/DeltekVision.application'
|
||||||
|
Run-Script -LivePSScript Install-DeltekVision -Arguments $VisionURL
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Enable-HyperVFeatures
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-AutodeskProductLicensing
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-ClearOutlookAutoCompleteCache
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-ClearQuickAccess
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-DellOptimizer
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-DisableAllOneDriveNotifications
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@echo off
|
||||||
|
title Disable Sleep While Plugged-In
|
||||||
|
powercfg /x -hibernate-timeout-ac 0
|
||||||
|
powercfg /x -disk-timeout-ac 0
|
||||||
|
powercfg /x -monitor-timeout-ac 0
|
||||||
|
powercfg /x -standby-timeout-ac 0
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-DisableWindowsTelemetry
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
|
||||||
|
# Set the edition of the ECB agent.
|
||||||
|
# Valid options are 'desktop' 'server' or 'vm'
|
||||||
|
$Edition = '{[Edition]}'
|
||||||
|
|
||||||
|
# Set the MSP360 username and password
|
||||||
|
$MSP360Username = '{[MSP360_Username]}'
|
||||||
|
$MSP360Password = '{[MSP360_Password]}'
|
||||||
|
|
||||||
|
# Run the installation script
|
||||||
|
Run-Script -LivePSScript Fix-EmberkomCloudBackupProductEdition
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-EnableGPUScheduling
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-EnableSystemRestore
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
|
||||||
|
# Set the TDR level
|
||||||
|
# Note: 1 = Disable, 3 = Recover (default)
|
||||||
|
[int]$TdrLevel = '{[TDR_Level]}'
|
||||||
|
|
||||||
|
# Set TDR delay
|
||||||
|
# Note: 2 = Default
|
||||||
|
[int]$TdrDelay = '{[TDR_Delay]}'
|
||||||
|
|
||||||
|
Run-Script -LivePSScript Fix-GPUTimeoutDetectionResponse
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-GroupPolicyUpdate
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-HighCPUUsage
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-HighMemoryUsage
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-MicrosoftTeams
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-OfficeModernAuth
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-OutlookUnblockAttachmentTypes
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-RebootIntoSafeMode
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-ReinstallAllAppXPackages
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-Service-QuickBooksDB31
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
rem Remove registry properties that add the Splashtop PDF Printer to the system
|
||||||
|
REG DELETE "HKEY_LOCAL_MACHINE\SOFTWARE\Splashtop Inc.\Splashtop Remote Server" /f /v PrinterINFa
|
||||||
|
REG DELETE "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Splashtop Inc.\Splashtop Remote Server" /f /v PrinterINFa
|
||||||
|
|
||||||
|
rem Restart the SplashtopRemoteService
|
||||||
|
net stop SplashtopRemoteService
|
||||||
|
net start SplashtopRemoteService
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-SplashtopService
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-SystemPrintQueue
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-UpdateLocalHosts
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-WindowsHealth
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-WindowsManagementInsturmentationWMI
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Fix-WindowsUpdate
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Get-BackupCapacityPlanningData
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#Requires -Version 5.0
|
||||||
|
# Syncro supplies DSAManifestSource, Zip, Upload, and DeleteAfterUpload.
|
||||||
|
# Preserve those caller variables for the repository script.
|
||||||
|
|
||||||
|
# This must run before downloading Tools.ps1, including on Server 2016.
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
|
||||||
|
$RmmToolsUrl = 'https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1'
|
||||||
|
$RmmToolsClient = New-Object Net.WebClient
|
||||||
|
Try {
|
||||||
|
$RmmToolsText = $RmmToolsClient.DownloadString($RmmToolsUrl)
|
||||||
|
$RmmToolsScript = [Scriptblock]::Create($RmmToolsText)
|
||||||
|
}
|
||||||
|
Catch {
|
||||||
|
Throw "Unable to download or parse Tools.ps1 from ${RmmToolsUrl}: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
Finally {
|
||||||
|
$RmmToolsClient.Dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Dot-source in this scope so the RMM input variables remain accessible.
|
||||||
|
. $RmmToolsScript
|
||||||
|
Run-Script -LivePSScript Get-DSAManifest
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Get-DisplayInformation
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
. ([ScriptBlock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
$DriveLetter = '{[DriveLetter]}'
|
||||||
|
Get-FileSizes -Path "${DriveLetter}:\" -Recurse -IncludeHidden -Largest 50 -Units 'GB'
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Get-InstalledSoftware
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Get-UserLogonActivity
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Get-WindowsAppXPackages
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Create-NetworkDrive -DriveLetter O: -Path "\\nexus\Office Drive" -Label "Office Drive"
|
||||||
|
Create-NetworkDrive -DriveLetter P: -Path "\\nexus\CWolfe Drive" -Label "Design Drive"
|
||||||
|
Create-NetworkDrive -DriveLetter Z: -Path "\\nexus\Accounting Drive" -Label "Accounting Drive"
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# The following section is by j.mcbride from https://community.syncromsp.com/t/powershell-7-2/7425/5
|
||||||
|
# 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-Output 'PowerShell 7 was not installed. Update PowerShell and try again.'
|
||||||
|
throw $Error
|
||||||
|
}
|
||||||
|
finally { exit $LASTEXITCODE }
|
||||||
|
}
|
||||||
|
# Input the actual script below this line
|
||||||
|
|
||||||
|
# The rest of this script is by Emberkom
|
||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
$HuduAPIKey = ''
|
||||||
|
$HuduBaseDomain = 'https://emberkom.huducloud.com'
|
||||||
|
$CompanyName = $CName
|
||||||
|
Run-Script -LivePSScript Hudu-DocumentADDS
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Install-4KVideoDownloader
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Install-AdobeConnect
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Install-AmazonCorretto
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
Run-Script -LivePSScript Install-AutodeskDesktopConnector
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||||
|
# $VisionURL = ''
|
||||||
|
Run-Script -LivePSScript Install-DeltekVision -Arguments $VisionURL
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user