Compare commits
428
Commits
a887e460aa
...
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 | ||
|
|
9409f000e0 | ||
|
|
0499a143ef | ||
|
|
5cd4e99465 | ||
|
|
8962142931 | ||
|
|
bb18c3436d | ||
|
|
72445c1701 | ||
|
|
983952ee99 | ||
|
|
ab2a1821d5 | ||
|
|
df7f635e26 | ||
|
|
c628f60289 | ||
|
|
5f4b4565de | ||
|
|
bbb4884818 | ||
|
|
31a06a9206 | ||
|
|
32b33cee34 | ||
|
|
c50ce1097f | ||
|
|
de68ede13a | ||
|
|
764eaac9b2 | ||
|
|
30cd1301ee | ||
|
|
f7e73061af | ||
|
|
d93a43d260 | ||
|
|
618cf7a7b7 | ||
|
|
a3278beb71 | ||
|
|
9d7c5d59ba | ||
|
|
99b87f468b | ||
|
|
2bc320480f | ||
|
|
60ffce3812 | ||
|
|
bb45975284 | ||
|
|
c275073c45 | ||
|
|
103122c1e0 | ||
|
|
ebbee7c0d0 | ||
|
|
9816d15165 | ||
|
|
0360329312 | ||
|
|
6a6c3fc9cf | ||
|
|
21cafbb931 | ||
|
|
01a04a1127 | ||
|
|
c1c7681370 | ||
|
|
70a7ddb75d | ||
|
|
b52984ff32 | ||
|
|
c32e74002b | ||
|
|
1a731c2b49 | ||
|
|
c2f5c06996 | ||
|
|
cc97f9e592 | ||
|
|
f21e5ff768 | ||
|
|
3929a896ed | ||
|
|
f6ebc2f0fa | ||
|
|
3f36b91570 | ||
|
|
20fb02e026 | ||
|
|
9747a38fab | ||
|
|
f09b44d15e | ||
|
|
3f6346d02c | ||
|
|
794fddda73 | ||
|
|
e0e254d799 | ||
|
|
6a47fb6a64 | ||
|
|
6d77d8fab4 | ||
|
|
e3db5cb6e7 | ||
|
|
aa48a738fa | ||
|
|
80979fc137 | ||
|
|
9644829b9e | ||
|
|
d4e716f15b | ||
|
|
8440c1ea05 | ||
|
|
19d2f40526 | ||
|
|
e0b39c591b | ||
|
|
9bb5239e61 | ||
|
|
fc0b916a38 | ||
|
|
e7b9b3b9dc | ||
|
|
6204176ec5 | ||
|
|
bf20a24cdb | ||
|
|
231da6ace4 | ||
|
|
4a7df59b1c | ||
|
|
d8c144ff76 | ||
|
|
c2ebb5cfeb | ||
|
|
31c8a2719b | ||
|
|
2cb74b37de | ||
|
|
223d8ea488 | ||
|
|
9358d85d27 | ||
|
|
0d1216a5cf | ||
|
|
c9502bf051 | ||
|
|
7ce453114b | ||
|
|
5207d8719f | ||
|
|
522f0abda3 | ||
|
|
8ace59d30a | ||
|
|
1c4aa415e1 | ||
|
|
183bafcba1 | ||
|
|
aa6c5f0179 | ||
|
|
3ada68d37f | ||
|
|
933105f0bf | ||
|
|
0fa22b397c | ||
|
|
fab9912fc8 | ||
|
|
35e3bcd03d | ||
|
|
ea77a6f7ee | ||
|
|
35dd913537 | ||
|
|
e4bffa2caa | ||
|
|
338b2915cf | ||
|
|
25ef1c9b2a | ||
|
|
7094ff6f8e | ||
|
|
fe2e50e456 | ||
|
|
e39606cd1b | ||
|
|
6bfc18a15f | ||
|
|
678b808e64 | ||
|
|
61990346d0 | ||
|
|
74559046c0 | ||
|
|
88b721a7f9 | ||
|
|
1cc289c119 | ||
|
|
1c5d7e26d6 | ||
|
|
a3825836f7 | ||
|
|
fd6ca90bb0 | ||
|
|
e138e811ce | ||
|
|
c804483441 | ||
|
|
6fc62ee5b2 | ||
|
|
bc33eedf19 | ||
|
|
5f61ae4758 | ||
|
|
999ec1a4a7 | ||
|
|
9d4739beba | ||
|
|
855f186aba | ||
|
|
10bd04c73d | ||
|
|
cdab3ef78b | ||
|
|
e900b65cc5 | ||
|
|
64d6b36bd0 | ||
|
|
5a08df43f0 | ||
|
|
ddfbd78490 | ||
|
|
ba077b572a | ||
|
|
d11dad75ca | ||
|
|
73f976c38a | ||
|
|
54d90ee99a | ||
|
|
8f97ffe818 | ||
|
|
c26632ca88 | ||
|
|
3f4ff32861 | ||
|
|
321ac1c3ea | ||
|
|
49749d4f03 | ||
|
|
a6d1172bfd | ||
|
|
c0eee62f1e | ||
|
|
f0b6075940 | ||
|
|
2f49d7d7e3 | ||
|
|
0d087fa747 | ||
|
|
a27c9a1607 | ||
|
|
55063da460 | ||
|
|
5a9c6e1dec | ||
|
|
afbc2e9b70 | ||
|
|
5b7cf4c7a2 | ||
|
|
0ea1382a11 | ||
|
|
e84adf9182 | ||
|
|
77aff2293a | ||
|
|
c2c8c23d2b | ||
|
|
eaf0afa2b5 | ||
|
|
a9d9ee9634 | ||
|
|
4ca356264e | ||
|
|
cb885412ef | ||
|
|
b516722d51 | ||
|
|
b2a480f709 | ||
|
|
4f6caa2869 | ||
|
|
97dc9bb343 | ||
|
|
4270756bdc | ||
|
|
357d5bfdd0 |
@@ -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 '-------------------------------------------------------------------------------'
|
||||
@@ -19,18 +19,18 @@ Foreach ( $path in $CURRENT_PATH.Split(";") )
|
||||
}
|
||||
}
|
||||
|
||||
LogMsg "Removed Paths:`n`r${REMOVE_PATH}"
|
||||
Write-Output "Removed Paths:`n`r${REMOVE_PATH}"
|
||||
|
||||
Try { Set-ItemProperty -Path $REG_ENVVAR -Name Path -Value $NEW_PATH }
|
||||
Catch
|
||||
{
|
||||
LogErr $_.Exception.Message
|
||||
LogErr "Failed to cleanup system path, using original values"
|
||||
Write-Error $_.Exception.Message
|
||||
Write-Error "Failed to cleanup system path, using original values"
|
||||
Try { Set-ItemProperty -Path $REG_ENVVAR -Name Path -Value $CURRENT_PATH }
|
||||
Catch
|
||||
{
|
||||
LogErr $_.Exception.Message
|
||||
LogErr "This is not good... !! DO NOT RESTART UNTIL SYSTEM PATH IS FIXED !!"
|
||||
Write-Error $_.Exception.Message
|
||||
Write-Error "This is not good... !! DO NOT RESTART UNTIL SYSTEM PATH IS FIXED !!"
|
||||
Exit
|
||||
}
|
||||
}
|
||||
+244
-80
@@ -1,111 +1,275 @@
|
||||
|
||||
. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||
|
||||
# When deleting temp files en masse, only delete files/folders older than the number of days specified here
|
||||
$OlderThan = 7
|
||||
$TimeDelta = New-TimeSpan -Days $OlderThan
|
||||
|
||||
# Only run this section if we have administrative privileges
|
||||
If ( IsAdmin )
|
||||
{
|
||||
LogMsg "Running with administrative privileges"
|
||||
|
||||
# Remove all *.evtx files from $SysTemp
|
||||
$EventLogs = Get-ChildItem -Path "${Env:SystemRoot}\TEMP\*" -File -Filter *.evtx
|
||||
If ( $EventLogs )
|
||||
{
|
||||
LogMsg "Deleting all event logs from ${Env:SystemRoot}\TEMP"
|
||||
# Remove all *.evtx files from system temporary files location
|
||||
$EventLogs = Get-ChildItem -Path "${Env:SystemRoot}\TEMP\*" -File -Filter *.evtx
|
||||
If ( $EventLogs ) {
|
||||
Write-Output "Deleting all event logs from ${Env:SystemRoot}\TEMP"
|
||||
ForEach ( $EventLog in $EventLogs ) { Remove-Item $EventLog -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
}
|
||||
|
||||
# Remove misc logs and error reports
|
||||
$MiscLogs = Get-ChildItem -Path "${Env:SystemRoot}\TEMP\*" -File -Include "${Env:COMPUTERNAME}-*.log", "AppxErrorReport_*.txt"
|
||||
If ( $MiscLogs )
|
||||
{
|
||||
LogMsg "Deleting misc logs from ${Env:SystemRoot}\TEMP"
|
||||
# Remove misc logs and error reports
|
||||
$MiscLogs = Get-ChildItem -Path "${Env:SystemRoot}\TEMP\*" -File -Include "${Env:COMPUTERNAME}-*.log", "AppxErrorReport_*.txt"
|
||||
If ( $MiscLogs ) {
|
||||
Write-Output "Deleting misc logs from ${Env:SystemRoot}\TEMP"
|
||||
ForEach ( $MiscLog in $MiscLogs ) { Remove-Item $MiscLog -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
}
|
||||
|
||||
# Remove archived CBS logs
|
||||
$CBSLogs = Get-ChildItem -Path "${Env:SystemRoot}\Logs\CBS\*" -File -Include "CbsPersist_*.log", "CbsPersist_*.cab"
|
||||
If ( $CBSLogs )
|
||||
{
|
||||
LogMsg "Deleting archived CBS logs from ${Env:SystemRoot}\Logs\CBS"
|
||||
# Remove archived CBS logs
|
||||
$CBSLogs = Get-ChildItem -Path "${Env:SystemRoot}\Logs\CBS\*" -File -Include "CbsPersist_*.log", "CbsPersist_*.cab"
|
||||
If ( $CBSLogs ) {
|
||||
$TotalBytes = 0
|
||||
ForEach ( $CBSLog in $CBSLogs) { $TotalBytes += $CBSLog.Length }
|
||||
$TotalSize = [math]::Round(($TotalBytes / 1MB),2)
|
||||
If ( $TotalSize -ge 200 ) {
|
||||
Write-Output "Total CBS log archive size: ${TotalSize}MB"
|
||||
Control-Service -Stop 'TrustedInstaller'
|
||||
Write-Output "Deleting primary CBS.log file"
|
||||
Remove-Item "${Env:SystemRoot}\Logs\CBS\CBS.log" -Force -ErrorAction SilentlyContinue
|
||||
Control-Service -Start 'TrustedInstaller'
|
||||
}
|
||||
Write-Output "Deleting archived CBS logs from ${Env:SystemRoot}\Logs\CBS"
|
||||
ForEach ( $CBSLog in $CBSLogs ) { Remove-Item $CBSLog -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
|
||||
# Remove all system temp files older than 7 days
|
||||
$OldTempFiles = Get-ChildItem -Path "${Env:SystemRoot}\TEMP" | Where-Object { $_.LastWriteTime -lt ((Get-Date) - $TimeDelta) }
|
||||
If ( $OldTempFiles ) {
|
||||
Write-Output "Deleting all temp files not modified in ${OlderThan} day(s) from ${Env:SystemRoot}\TEMP"
|
||||
ForEach ( $OldTempFile in $OldTempFiles ) { Remove-Item $OldTempFile.FullName -Force -Recurse -ErrorAction SilentlyContinue }
|
||||
}
|
||||
|
||||
# Remove all old files from the Recycle Bin
|
||||
$UserRecycleBins = Get-ChildItem -Path "${Env:SystemDrive}\`$Recycle.Bin" -Directory -ErrorAction SilentlyContinue
|
||||
If ( ($UserRecycleBins | Measure-Object).Count -gt 0 ) {
|
||||
Write-Output "Deleting all recycle bin items older than ${OlderThan} days from:"
|
||||
ForEach ( $userRecycleBin in $UserRecycleBins ) {
|
||||
$userRecycleBinFullName = $userRecycleBin.FullName
|
||||
Write-Output " - $userRecycleBinFullName"
|
||||
$items = Get-ChildItem -Path $userRecycleBin.FullName -Recurse -ErrorAction SilentlyContinue
|
||||
ForEach ( $item in $items ) {
|
||||
If ( $item.LastWriteTime -lt $TimeDelta ) {
|
||||
Try { Remove-Item -Path $item.FullName -Force -ErrorAction Continue }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
# Remove all system temp files older than 7 days
|
||||
$OldTempFiles = Get-ChildItem -Path "${Env:SystemRoot}\TEMP" | Where { $_.LastWriteTime -lt ((Get-Date) - $TimeDelta) }
|
||||
If ( $OldTempFiles )
|
||||
{
|
||||
LogMsg "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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Turn off system hibernation if it's in use
|
||||
If ( Test-Path "${Env:SystemDrive}\hiberfil.sys" )
|
||||
{
|
||||
LogMsg "Turning off system hibernation"
|
||||
# Turn off system hibernation if it's in use
|
||||
If ( Test-Path "${Env:SystemDrive}\hiberfil.sys" ) {
|
||||
Write-Output "Turning off system hibernation"
|
||||
Try { Start-Process "powercfg" -ArgumentList "-h off" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
}
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
# Remove PDCRevocation dump files
|
||||
$PDCRevocationDumpFiles = Get-ChildItem -Path "${Env:SystemRoot}\LiveKernelReports\*" -File -Include "PDCRevocation-*.dmp"
|
||||
If ( $PDCRevocationDumpFiles ) {
|
||||
LogMsg "Deleting PDCRevocation dump files from ${Env:SystemRoot}\LiveKernelReports"
|
||||
ForEach ( $PDCRevocationDump in $PDCRevocationDumpFiles ) { Remove-Item $PDCRevocationDump -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
# Remove PDCRevocation dump files
|
||||
$PDCRevocationDumpFiles = Get-ChildItem -Path "${Env:SystemRoot}\LiveKernelReports\*" -File -Include "PDCRevocation-*.dmp"
|
||||
If ( $PDCRevocationDumpFiles ) {
|
||||
Write-Output "Deleting PDCRevocation dump files from ${Env:SystemRoot}\LiveKernelReports"
|
||||
ForEach ( $PDCRevocationDump in $PDCRevocationDumpFiles ) { Remove-Item $PDCRevocationDump.FullName -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
|
||||
# Remove Genetec application crash dumps
|
||||
If ( Test-Path "${Env:ProgramData}\Genetec\Dumps" ) {
|
||||
# Remove Genetec application crash dumps
|
||||
If ( Test-Path "${Env:ProgramData}\Genetec\Dumps" ) {
|
||||
$GenetecCrashDumps = Get-ChildItem -Path "${Env:ProgramData}\Genetec\Dumps"
|
||||
If ( $GenetecCrashDumps )
|
||||
{
|
||||
LogMsg "Deleting Genetec application crash dumps"
|
||||
Write-Output "Deleting Genetec application crash dumps"
|
||||
Try { ForEach ( $dump in $GenetecCrashDumps ) { Remove-Item $dump.FullName -Force -Recurse -ErrorAction SilentlyContinue } }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
}
|
||||
|
||||
# Defragment Windows Elastic Search database
|
||||
$ESDB = "${Env:ProgramData}\Microsoft\Search\Data\Applications\Windows\Windows.edb"
|
||||
If ( Test-Path $ESDB ) {
|
||||
Try {
|
||||
Write-Output "Found Windows Elastic Search database: ""${ESDB}"""
|
||||
Write-Output " - Disabling Windows Search service"
|
||||
Start-Process "sc.exe" -ArgumentList "config wsearch start= disabled" -Wait
|
||||
Write-Output " - Stopping Windows Search Service"
|
||||
Start-Process "net.exe" -ArgumentList "stop wsearch" -Wait
|
||||
Write-Output " - Defragmenting database file"
|
||||
Start-Process "esentutl.exe" -ArgumentList "/d ""${ESDB}""" -Wait
|
||||
Write-Output " - Enabling Windows Search Service"
|
||||
Start-Process "sc.exe" -ArgumentList "config wsearch start= delayed-auto" -Wait
|
||||
Write-Output " - Starting Windows Search Service"
|
||||
Start-Process "net.exe" -ArgumentList "start wsearch" -Wait
|
||||
}
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
}
|
||||
|
||||
# Remove any Autodesk installers at their default location
|
||||
$ADSKInst = "${Env:SystemDrive}\Autodesk"
|
||||
If ( Test-Path $ADSKInst ) {
|
||||
$foldersToDelete = Get-ChildItem -Path $ADSKInst -ErrorAction SilentlyContinue | Where-Object { ($_.PSIsContainer) -and ($_.LastWriteTime -lt ((Get-Date) - $TimeDelta)) -and ( $_.Name -ne "WI") }
|
||||
ForEach ( $f in $foldersToDelete ) {
|
||||
$path = $f.FullName
|
||||
Write-Output "Deleting Autodesk installer directory: ""${path}"""
|
||||
Try { Remove-Item $f.FullName -Force -Recurse -ErrorAction SilentlyContinue }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
}
|
||||
|
||||
# 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
|
||||
ForEach ( $userProfile in $(Get-UserProfiles) ) {
|
||||
|
||||
# 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) }
|
||||
If ( $OldTempFiles ) {
|
||||
Write-Output "Deleting all temp files not modified in ${OlderThan} day(s) from ""${userProfile}\AppData\Local\Temp"""
|
||||
ForEach ( $OldTempFile in $OldTempFiles ) { Remove-Item $OldTempFile.FullName -Force -Recurse -ErrorAction SilentlyContinue }
|
||||
}
|
||||
|
||||
# Remove AutoCAD temp files
|
||||
If (-not (IsRunning -Name "acad") ) {
|
||||
$AutoCADTempFiles = Get-ChildItem -Path "${Env:SystemDrive}\Users" | Where-Object { $_.PSIsContainer -eq $true } | ForEach-Object { Get-ChildItem -Path $($_.FullName + "\AppData\Local\Temp\*") -File -Include '*.ac$' -ErrorAction SilentlyContinue }
|
||||
$AutoCADTempFiles = Get-ChildItem -Path "${userProfile}\AppData\Local\Temp\*" -File -Include '*.ac$' -ErrorAction SilentlyContinue
|
||||
If ( $AutoCADTempFiles ) {
|
||||
LogMsg "Deleting AutoCAD temp files for all users"
|
||||
ForEach ( $tempfile in $AutoCADTempFiles ) { Remove-Item $tempfile -Force -ErrorAction SilentlyContinue }
|
||||
Write-Output "Deleting AutoCAD temp files"
|
||||
ForEach ( $tempfile in $AutoCADTempFiles ) {
|
||||
Try { Remove-Item $tempfile.FullName -Force -ErrorAction SilentlyContinue }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Silently run cleanmgr.exe with default settings
|
||||
LogMsg "Running cleanmgr.exe with default settings"
|
||||
Try { Start-Process "cleanmgr.exe" -ArgumentList "/VERYLOWDISK" }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
# Apple iTunes data
|
||||
# If (-not (IsRunning -Name 'itunes') ) {
|
||||
|
||||
# # Remove Apple software updates downloaded from iTunes
|
||||
# $IPSWBasePath = "${userProfile}\AppData\Roaming\Apple Computer\iTunes"
|
||||
# $IPSW = @()
|
||||
# # iPhone update files
|
||||
# ForEach ( $updateFile in $(Get-ChildItem -Path "${IPSWBasePath}\iPhone Software Updates\*" -File -Include '*.ipsw' -ErrorAction SilentlyContinue) ) {
|
||||
# $IPSW += ,@($updateFile.FullName)
|
||||
# }
|
||||
# # iPad update files
|
||||
# ForEach ( $updateFile in $(Get-ChildItem -Path "${IPSWBasePath}\iPad Software Updates\*" -File -Include '*.ipsw' -ErrorAction SilentlyContinue) ) {
|
||||
# $IPSW += ,@($updateFile.FullName)
|
||||
# }
|
||||
# # iPod update files
|
||||
# ForEach ( $updateFile in $(Get-ChildItem -Path "${IPSWBasePath}\iPod Software Updates\*" -File -Include '*.ipsw' -ErrorAction SilentlyContinue) ) {
|
||||
# $IPSW += ,@($updateFile.FullName)
|
||||
# }
|
||||
|
||||
# # Remove Apple mobile applications downloaded from iTunes
|
||||
# $IPA = @()
|
||||
# ForEach ( $mobileApp in $(Get-ChildItem -Path "${userProfile}\Music\iTunes\iTunes Media\Mobile Applications\*" -File -Include '*.ipa' -ErrorAction SilentlyContinue) ) {
|
||||
# $IPA += ,@($updateFile.FullName)
|
||||
# }
|
||||
|
||||
# If ( $IPSW ) {
|
||||
# Write-Output "Found Apple software update files"
|
||||
# ForEach ( $path in $IPSW ) {
|
||||
# Write-Output " - Deleting: ""${path}"""
|
||||
# Try { Remove-Item -Path $path -Force -ErrorAction SilentlyContinue }
|
||||
# Catch { Write-Error $_.Exception.Message }
|
||||
# }
|
||||
# }
|
||||
|
||||
# If ( $IPA ) {
|
||||
# Write-Output "Found Apple mobile application files"
|
||||
# ForEach ( $path in $IPA ) {
|
||||
# Write-Output " - Deleting: ""${path}"""
|
||||
# Try { Remove-Item -Path $path -Force -ErrorAction SilentlyContinue }
|
||||
# Catch { Write-Error $_.Exception.Message }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
|
||||
# Remove old downloaded application files
|
||||
$OldDownloadedApps = (Get-ChildItem -Path "${userProfile}\Downloads\*" -File -Filter '*.exe' -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -lt ((Get-Date) - $TimeDelta) })
|
||||
If ( $OldDownloadedApps ) {
|
||||
Write-Output "Deleting old downloaded app files"
|
||||
ForEach ( $oldDownloadedApp in $OldDownloadedApps ) {
|
||||
$path = $oldDownloadedApp.FullName
|
||||
Write-Output " - Deleting: ""${path}"""
|
||||
Try { Remove-Item -Path $path -Force -ErrorAction SilentlyContinue }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
}
|
||||
|
||||
# Remove duplicate downloads
|
||||
# Separate the possible duplicates from the rest
|
||||
$PossibleDuplicates = @()
|
||||
$NotDuplicates = @()
|
||||
ForEach ( $file in (Get-ChildItem -Path "${userProfile}\Downloads\*" -File -ErrorAction SilentlyContinue) ) {
|
||||
If ( $file.BaseName -match '\s\(\d*\)$' ) {
|
||||
$PossibleDuplicates += ,@($file)
|
||||
} Else {
|
||||
$NotDuplicates += ,@($file)
|
||||
}
|
||||
}
|
||||
# Loop through all files
|
||||
ForEach ( $pd in $PossibleDuplicates ) {
|
||||
ForEach ( $nd in $NotDuplicates ) {
|
||||
|
||||
# Match the first part of $pd to $nd
|
||||
If ( [regex]::Match($pd.BaseName,'^(.*)(\s\(\d*\))$').Groups[1].Value -eq $nd.BaseName ) {
|
||||
|
||||
# Check for equal file size
|
||||
If ( $(Get-Item -Path $pd.FullName).Length -eq $(Get-Item -Path $nd.FullName).Length ) {
|
||||
|
||||
# Make sure the file still exists before writing any log messages
|
||||
$ToDelete = $pd.FullName
|
||||
If ( Test-Path $ToDelete ) {
|
||||
|
||||
# Delete the duplicate file
|
||||
Write-Output "Deleting duplicate download: ""${ToDelete}"""
|
||||
Try { Remove-Item -Path $ToDelete -Force -ErrorAction SilentlyContinue }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Remove old Outlook backup files
|
||||
$OldOutlookBackupFiles = Get-ChildItem -Path "${userProfile}\AppData\Local\Microsoft\Outlook\*" -File -Filter '*.bak' -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -lt ((Get-Date) - $TimeDelta) }
|
||||
If ( $OldOutlookBackupFiles ) {
|
||||
Write-Output "Found Outlook backup files older than ${OlderThan} days:"
|
||||
ForEach ( $file in $OldOutlookBackupFiles ) {
|
||||
$path = $file.FullName
|
||||
Write-Output "Deleting: ""${path}"""
|
||||
Try { Remove-Item -Path $path -Force -ErrorAction SilentlyContinue }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Only run this section if we don't have administrative privileges
|
||||
Else
|
||||
{
|
||||
LogMsg "Running without administrative privileges"
|
||||
|
||||
# Remove all system temp files older than 7 days
|
||||
$OldTempFiles = Get-ChildItem -Path $Env:TEMP | Where { $_.LastWriteTime -lt ((Get-Date) - $TimeDelta) }
|
||||
If ( $OldTempFiles )
|
||||
{
|
||||
LogMsg "Deleting all temp files not modified in ${OlderThan} day(s) from ${Env:TEMP}"
|
||||
ForEach ( $OldTempFile in $OldTempFiles ) { Remove-Item $OldTempFile -Force -Recurse -ErrorAction SilentlyContinue }
|
||||
}
|
||||
|
||||
# Remove AutoCAD temp files
|
||||
If (-not (IsRunning -Name "acad") ) {
|
||||
$AutoCADTempFiles = Get-ChildItem -Path "${Env:TEMP}\*" -File -Include '*.ac$' -ErrorAction SilentlyContinue
|
||||
If ( $AutoCADTempFiles ) {
|
||||
LogMsg "Deleting AutoCAD temp files"
|
||||
ForEach ( $tempfile in $AutoCADTempFiles ) { Remove-Item $tempfile -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
}
|
||||
|
||||
# Silently run cleanmgr.exe with default settings
|
||||
LogMsg "Running cleanmgr.exe with default settings"
|
||||
Try { Start-Process "cleanmgr.exe" -ArgumentList "/VERYLOWDISK" }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
}
|
||||
# Silently run cleanmgr.exe with default settings
|
||||
# TODO: This does not run silently - commenting out for now!
|
||||
#Try {
|
||||
# Write-Output "Running cleanmgr.exe with default settings for very low disk space"
|
||||
# Start-Process "cleanmgr.exe" -ArgumentList "/VERYLOWDISK"
|
||||
# Write-Output " - Finished"
|
||||
#}
|
||||
#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"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,12 +3,12 @@ $RegKey = 'HKCU:SOFTWARE\Microsoft\Windows\CurrentVersion\ImmersiveShell'
|
||||
## Make sure endpoint is running at least Windows 8
|
||||
If ( IsWindowsVersion -ge '6.3' )
|
||||
{
|
||||
LogMsg "Disabling tabled mode"
|
||||
Write-Output "Disabling tabled mode"
|
||||
Try
|
||||
{
|
||||
Set-ItemProperty $RegKey -Name 'TabletMode' -Value 0
|
||||
Set-ItemProperty $RegKey -Name 'SignInMode' -Value 1
|
||||
}
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
Else { LogMsg "Tablet mode is only supported on Windows 8 and higher" }
|
||||
Else { Write-Output "Tablet mode is only supported on Windows 8 and higher" }
|
||||
@@ -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 }
|
||||
+7
-7
@@ -5,21 +5,21 @@ $FolderNewName = "${FolderToRename}${RenameSuffix}"
|
||||
# Kill processes that can interfere
|
||||
'reg', 'AgentPackageTaskManagement' | ForEach-Object { Get-Process -Name $_ -ErrorAction SilentlyContinue | Stop-Process -Force }
|
||||
|
||||
LogMsg "Stopping Atera agent"
|
||||
Write-Output "Stopping Atera agent"
|
||||
Stop-Service -Name 'AteraAgent' -Force
|
||||
|
||||
If ( Test-Path $FolderNewName ) {
|
||||
LogMsg "Deleting previously renamed folder: ${FolderNewName}"
|
||||
Write-Output "Deleting previously renamed folder: ${FolderNewName}"
|
||||
Try { Remove-Item $FolderNewName -Recurse -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
LogMsg "Stopping AgentPackageMonitoring process"
|
||||
Write-Output "Stopping AgentPackageMonitoring process"
|
||||
Get-Process -Name "AgentPackageMonitoring" -ErrorAction SilentlyContinue | Stop-Process -Force
|
||||
|
||||
LogMsg "Renaming ${FolderToRename}"
|
||||
Write-Output "Renaming ${FolderToRename}"
|
||||
Try { Rename-Item $FolderToRename $FolderNewName -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
LogMsg "Starting Atera agent"
|
||||
Write-Output "Starting Atera agent"
|
||||
Start-Service -Name 'AteraAgent'
|
||||
@@ -1,30 +1,30 @@
|
||||
. $([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
|
||||
$URL = 'https://knowledge.autodesk.com/sites/default/files/file_downloads/AdskLicensingSupportTool-2.2.0.502-win.zip'
|
||||
#. $([Scriptblock]::Create((New-Object Net.WebClient).DownloadString('https://dev.emberkom.com/emberkom/management-scripts/raw/branch/master/Tools.ps1')))
|
||||
# https://www.autodesk.com/support/technical/article/caas/tsarticles/ts/6IFPpIksTDfsCzp1JwIH5k.html
|
||||
$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
|
||||
$LicensingSupportTool = Download-File -URL $URL
|
||||
|
||||
# Extract the downloaded zip file
|
||||
Unzip-Object $LicensingSupportTool
|
||||
Expand-Archive -Path $LicensingSupportTool
|
||||
|
||||
$WorkingDir = '{0}\{1}' -f (Split-Path -Path $LicensingSupportTool -Parent), "AdskLicensingSupportTool"
|
||||
|
||||
$ALST = "${WorkingDir}\AdskLicensingSupportTool.exe"
|
||||
If ( Test-Path $ALST ) {
|
||||
LogMsg "Resetting all Autodesk product licenses to named-user licenses"
|
||||
Write-Output "Resetting all Autodesk product licenses to named-user licenses"
|
||||
Try { Start-Process $ALST -ArgumentList '-r ALL:User' -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
If ( Test-Path $LicensingSupportTool ) {
|
||||
LogMsg "Deleting ""${LicensingSupportTool}"""
|
||||
Write-Output "Deleting ""${LicensingSupportTool}"""
|
||||
Try { Remove-Item $LicensingSupportTool -Force -ErrorAction SilentlyContinue }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
If ( Test-Path $WorkingDir ) {
|
||||
LogMsg "Deleting ""${WorkingDir}"""
|
||||
Write-Output "Deleting ""${WorkingDir}"""
|
||||
Try { Remove-Item $WorkingDir -Force -Recurse -ErrorAction SilentlyContinue }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,3 +1,3 @@
|
||||
LogMsg "Clearing Outlook auto complete cache"
|
||||
Write-Output "Clearing Outlook auto complete cache"
|
||||
Try { Start-Process "outlook.exe" -ArgumentList "/CleanAutoCompleteCache" -ErrorAction Stop }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
@@ -6,16 +6,16 @@ If ( IsWindowsVersion -ge "10.0" ) {
|
||||
$FILEDIR = Split-Path -Path $FILE -Parent
|
||||
|
||||
If ( Test-Path $FILE ) {
|
||||
LogMsg "Clearing Quick Access links"
|
||||
Write-Output "Clearing Quick Access links"
|
||||
Try { Remove-Item $FILE -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
} ElseIf ( Test-Path $FILEDIR ) {
|
||||
LogMsg "Standard Quick Access link list is not found, clearing out all automatic destinations instead"
|
||||
Write-Output "Standard Quick Access link list is not found, clearing out all automatic destinations instead"
|
||||
Try { Get-ChildItem "${FILEDIR}\*.*" | Where-Object { -not $_.PSIsContainer } | Remove-Item -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
} Else {
|
||||
LogMsg "Quick Access lists cannot be found!"
|
||||
Write-Output "Quick Access lists cannot be found!"
|
||||
}
|
||||
} Else {
|
||||
LogMsg "Quick Access is only available on Windows 10 or greater"
|
||||
Write-Output "Quick Access is only available on Windows 10 or greater"
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
setlocal
|
||||
cls
|
||||
|
||||
call :configure-service AteraAgent
|
||||
call :configure-service Syncro
|
||||
call :configure-service SyncroLive
|
||||
call :configure-service SyncroOvermind
|
||||
call :configure-service SplashtopRemoteService
|
||||
goto :EOF
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Dell Optimizer
|
||||
If ( Test-Path "${Env:ProgramFiles}\Dell\DellOptimizer\do-cli.exe" ) { $DellOptimizerCli = "${Env:ProgramFiles}\Dell\DellOptimizer\do-cli.exe" }
|
||||
ElseIf ( Test-Path "${Env:ProgramFiles(x86)}\Dell\DellOptimizer\do-cli.exe" ) { $DellOptimizerCli = "${Env:ProgramFiles(x86)}\Dell\DellOptimizer\do-cli.exe" }
|
||||
If ( $DellOptimizerCli ) {
|
||||
Write-Output "Dell Optimizer"
|
||||
Try {
|
||||
# Disable Network Settings
|
||||
Write-Output "Disabling network settings"
|
||||
Start-Process -FilePath "C:\Program Files\Dell\DellOptimizer\do-cli.exe" -ArgumentList '/configure -name=Network.State -value=false' -Wait
|
||||
|
||||
# Disable Presence Detection
|
||||
Write-Output "Disabling presence detection"
|
||||
Start-Process -FilePath "C:\Program Files\Dell\DellOptimizer\do-cli.exe" -ArgumentList '/configure -name=PresenceDetection.State -value=false' -Wait
|
||||
}
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
} Else { Write-Output "Dell Optimizer command line application cannot be found!" }
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
[HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\16.0\Outlook\Options\General]
|
||||
"HideNewOutlookToggle"=dword:00000001
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# Disable telemetry
|
||||
[Microsoft.Win32.Registry]::SetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\DataCollection","AllowTelemetry",0,[Microsoft.Win32.RegistryValueKind]::DWord)
|
||||
@@ -0,0 +1,34 @@
|
||||
# This script requires the $Edition variable is set from the RMM script
|
||||
If (! $Edition ) { $Edition = 'desktop' }
|
||||
|
||||
# Set the correct download URL and agent edition
|
||||
switch ( $Edition.ToLower() ) {
|
||||
'server' { $AgentEdition = 'baremetal' }
|
||||
'desktop' { $AgentEdition = 'desktop' }
|
||||
'vm' { $AgentEdition = 'vmedition' }
|
||||
default { $AgentEdition = 'desktop' }
|
||||
}
|
||||
|
||||
# Install and import additional Powershell module
|
||||
Import-MSP360Module
|
||||
|
||||
# Exit this script if the agent is not installed
|
||||
If ( !($(Get-MBSAgent -ErrorAction SilentlyContinue)) ) {
|
||||
Microsoft.Powershell.Utility\Write-Output "Backup agent is not installed. This script will now exit."
|
||||
Return
|
||||
}
|
||||
|
||||
# Set the agent edition
|
||||
Write-Output "Setting Emberkom Cloud Backup agent edition to ""$AgentEdition"""
|
||||
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 }
|
||||
|
||||
# Delete the desktop icon
|
||||
Write-Output "Removing desktop icon"
|
||||
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
|
||||
@@ -0,0 +1,11 @@
|
||||
# Disable unattended sleep timeout (if computer was woken up by an unattended process, this timer kicks in)
|
||||
# The default value for this setting is 120 (2 minutes)
|
||||
# Aliases:
|
||||
# SUB_SLEEP = 238c9fa8-0aad-41ed-83f4-97be242c8f20
|
||||
# UNATTENDSLEEP = 7bc4a2f9-d8fc-4469-b07b-33eb785aaca0
|
||||
$UnattendedSleepTimeout = 0
|
||||
Try {
|
||||
Start-Process "powercfg.exe" -ArgumentList "/setacvalueindex SCHEME_CURRENT SUB_SLEEP UNATTENDSLEEP ${UnattendedSleepTimeout}" -Wait
|
||||
Start-Process "powercfg.exe" -ArgumentList "/setdcvalueindex SCHEME_CURRENT SUB_SLEEP UNATTENDSLEEP ${UnattendedSleepTimeout}" -Wait
|
||||
}
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
@@ -0,0 +1,42 @@
|
||||
$key = "HKLM:\SYSTEM\CurrentControlSet\Control\GraphicsDrivers"
|
||||
$name = "HwSchMode"
|
||||
|
||||
# Check to see if GPU scheduling is enabled
|
||||
$HardwareGPUSchedulingEnabled = $false
|
||||
If ( Test-RegistryProperty -Path $key -Name $name ) {
|
||||
$setting = (Get-ItemPropertyValue -Path $key -Name $name -ErrorAction SilentlyContinue)
|
||||
If ( $setting -eq 2 ) {
|
||||
Write-Output "Hardware assisted GPU scheduling is already enabled"
|
||||
$HardwareGPUSchedulingEnabled = $true
|
||||
}
|
||||
}
|
||||
If ( $HardwareGPUSchedulingEnabled -eq $false ) {
|
||||
Write-Output "Hardware assisted GPU scheduling is disabled"
|
||||
$HardwareGPUSchedulingEnabled = $false
|
||||
}
|
||||
|
||||
# Find a GPU in the system so we can enable GPU scheduling
|
||||
If ( $HardwareGPUSchedulingEnabled -eq $false ) {
|
||||
Write-Output " - Attempting to find a discrete Nvidia or AMD GPU"
|
||||
$GPUs = (Get-WmiObject Win32_VideoController).Name
|
||||
$FoundDiscreteGPU = $false
|
||||
ForEach ( $gpu in $GPUs ) {
|
||||
If ( $gpu -ilike "*NVIDIA*" ) {
|
||||
$FoundDiscreteGPU = $true
|
||||
}
|
||||
}
|
||||
|
||||
# If we found a discrete GPU, enable hardware assisted GPU scheduling
|
||||
If ( $FoundDiscreteGPU -eq $true ) {
|
||||
Write-Output " - GPU found!"
|
||||
Write-Output "Enabling hardware assisted GPU scheduling"
|
||||
Try {
|
||||
New-ItemProperty -Path $key -Name $name -PropertyType DWord -Value 2 -Force -ErrorAction Stop
|
||||
Write-Output "A restart is required before this change will take effect"
|
||||
}
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
} Else {
|
||||
Write-Output " - No GPU was found"
|
||||
Write-Output "Hardware assisted GPU scheduling will not be enabled"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
If ( $null -ne $(Get-ComputerRestorePoint) ) { Write-Output "System Restore is already enabled" ; Exit }
|
||||
Write-Output "Enabling System Restore"
|
||||
Try { Enable-ComputerRestore -Drive $Env:SystemDrive }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
@@ -0,0 +1,30 @@
|
||||
# Exit script if required variables are not defined
|
||||
If ( !$TdrLevel ) { Write-Error "TdrLevel must be defined, this script will now exit." ; Exit }
|
||||
If ( !$TdrDelay ) { Write-Error "TdrDelay must be defined, this script will now exit." ; Exit }
|
||||
|
||||
# Set the root registry path
|
||||
$RegPath = 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers'
|
||||
|
||||
# Get/set the TDR level
|
||||
$ExistingTdrLevel = [Microsoft.Win32.Registry]::GetValue($RegPath,'TdrLevel',3)
|
||||
If ( $TdrLevel -ne $ExistingTdrLevel ) {
|
||||
# Set the TDR level
|
||||
# Note: 1 = Disable, 3 = Recover (default)
|
||||
Write-Output "Existing TDR level: ${ExistingTdrLevel}"
|
||||
Write-Output "Changing TDR level to: ${TdrLevel}"
|
||||
[Microsoft.Win32.Registry]::SetValue($RegPath,'TdrLevel',$TdrLevel,[Microsoft.Win32.RegistryValueKind]::DWord)
|
||||
} Else {
|
||||
Write-Output "TDR level is already set to ${TdrLevel}"
|
||||
}
|
||||
|
||||
# Get/set the TDR delay
|
||||
$ExistingTdrValue = [Microsoft.Win32.Registry]::GetValue($RegPath,'TdrDelay',2)
|
||||
If ( $TdrDelay -ne $ExistingTdrValue ) {
|
||||
# Setting TDR timeout to $TdrDelay seconds
|
||||
# Note: 2 = Default
|
||||
Write-Output "Existing TDR delay: ${ExistingTdrValue}"
|
||||
Write-Output "Changing TDR delay to: ${TdrDelay}"
|
||||
[Microsoft.Win32.Registry]::SetValue($RegPath,'TdrDelay',$TdrDelay,[Microsoft.Win32.RegistryValueKind]::DWord)
|
||||
} Else {
|
||||
Write-Output "TDR delay is already set to ${TdrDelay}"
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
Try { Start-Process "gpupdate" -ArgumentList '/force' -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
@@ -1 +1,2 @@
|
||||
Stop-MemoryHog -AppName 'searchapp' -MemoryLimit 2048
|
||||
#Stop-MemoryHog -AppName 'AgentPackageProgramManagement' -MemoryLimit 1024
|
||||
@@ -0,0 +1 @@
|
||||
Get-AppXPackage *WindowsStore* -AllUsers | ForEach-Object {Add-AppxPackage -DisableDevelopmentMode -Register "$($_.InstallLocation)\AppXManifest.xml"}
|
||||
@@ -5,23 +5,23 @@ If ( Test-Path "${Env:LocalAppData}\Microsoft\Teams\Update.exe" ) {
|
||||
$ProcessList = (Get-Process -Name 'teams' -ErrorAction SilentlyContinue)
|
||||
|
||||
If ( $ProcessList ) {
|
||||
LogMsg "Stopping Microsoft Teams"
|
||||
Write-Output "Stopping Microsoft Teams"
|
||||
Try { $ProcessStopped = $true ; $ProcessList | Stop-Process -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
} Else { $ProcessStopped = $false }
|
||||
|
||||
# Remove all cached content
|
||||
$CacheDir = "${Env:AppData}\Microsoft\Teams"
|
||||
If ( Test-Path $CacheDir ) {
|
||||
LogMsg "Removing all cached data for Microsoft Teams"
|
||||
Write-Output "Removing all cached data for Microsoft Teams"
|
||||
Try { Remove-Item $CacheDir -Recurse -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
} Else { LogMsg "Could not clear cached data for Microsoft Teams because the directory could not be found: ${CacheDir}" }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
} Else { Write-Output "Could not clear cached data for Microsoft Teams because the directory could not be found: ${CacheDir}" }
|
||||
|
||||
# Restart Teams if we needed to close it before clearing the cache
|
||||
If ( $ProcessStopped ) {
|
||||
LogMsg "Starting Microsoft Teams"
|
||||
Write-Output "Starting Microsoft Teams"
|
||||
Try { Start-Process "${Env:LocalAppData}\Microsoft\Teams\Update.exe" -ArgumentList "--processStart ""Teams.exe""" }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
} Else { LogMsg "Cannot clear cache for Microsoft Teams because it isn't installed" }
|
||||
} Else { Write-Output "Cannot clear cache for Microsoft Teams because it isn't installed" }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
$REG_PATH = 'HKCU:\SOFTWARE\Microsoft\Exchange'
|
||||
$REG_PROP = 'AlwaysUseMSOAuthForAutoDiscover'
|
||||
If ( !((Get-ItemProperty -Path $REG_PATH -Name $REG_PROP -ErrorAction SilentlyContinue).$REG_PROP) ) {
|
||||
LogMsg "Adding registry entry: ""${REG_PATH}\${REG_PROP}"" = 1"
|
||||
Write-Output "Adding registry entry: ""${REG_PATH}\${REG_PROP}"" = 1"
|
||||
Try { New-ItemProperty -Path $REG_PATH -Name $REG_PROP -PropertyType DWord -Value 1 | Out-Null }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
Else { LogMsg "The registry path already exists: ""${REG_PATH}\${REG_PROP}""" }
|
||||
Else { Write-Output "The registry path already exists: ""${REG_PATH}\${REG_PROP}""" }
|
||||
|
||||
@@ -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)
|
||||
@@ -34,10 +34,10 @@ Function IsWindowsVersion {
|
||||
|
||||
# Make sure we're running at least Windows 10 1803 but not higher than Windows 10 21H2
|
||||
# This is because the Group Policy setting used below was introduced in 1803, so anything before that won't have any effect
|
||||
If ( (IsWindowsVersion -ge "10.0.17134") -and (IsWindowsVersion -le "10.0.19044") ) {
|
||||
If ( (IsWindowsVersion -ge "10.0.17134") -and (IsWindowsVersion -le "10.0.19045") ) {
|
||||
|
||||
# This is the version of Windows 10 you want to update to, but no further
|
||||
$MaxWindowsVersion = "21H2"
|
||||
$MaxWindowsVersion = "22H2"
|
||||
|
||||
$BaseRegPath = "HKLM:SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"
|
||||
If (-not (Test-Path $BaseRegPath) ) { New-Item -Path $BaseRegPath -Force | Out-Null }
|
||||
|
||||
@@ -6,13 +6,16 @@ set /a REBOOT=0
|
||||
|
||||
echo.
|
||||
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 "Cloud Backup Service Remote Management"
|
||||
|
||||
:schedule-normal-reboot
|
||||
call :reset-errorlevel
|
||||
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 (
|
||||
set /a REBOOT=%REBOOT%+1
|
||||
echo ERROR: could not configure reboot into normal mode
|
||||
@@ -20,7 +23,7 @@ if %ERRORLEVEL% GTR 0 (
|
||||
|
||||
:configure-reboot-safemodewithnetworking
|
||||
call :reset-errorlevel
|
||||
"%SystemRoot%\System32\bcdedit.exe" /set {current} safeboot network
|
||||
bcdedit /set {current} safeboot network
|
||||
if %ERRORLEVEL% GTR 0 (
|
||||
set /a REBOOT=%REBOOT%+1
|
||||
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 @@
|
||||
Get-AppxPackage -AllUsers | ForEach-Object { Add-AppxPackage -DisableDevelopmentMode -Register “$($_.InstallLocation)\AppXManifest.xml” }
|
||||
@@ -7,9 +7,9 @@ If (! ($(Get-Service -Name 'spooler').StartType -eq "Disabled") ) {
|
||||
Control-Service -Stop -Name $SERVICE
|
||||
|
||||
## Delete all print jobs
|
||||
LogMsg "Deleting all print jobs from ""${Env:WINDIR}\System32\spool\PRINTERS"""
|
||||
Write-Output "Deleting all print jobs from ""${Env:WINDIR}\System32\spool\PRINTERS"""
|
||||
Try { Remove-Item "${Env:WINDIR}\System32\spool\PRINTERS\*" -Recurse }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
## Start the spooler service
|
||||
Control-Service -Start -Name $SERVICE
|
||||
|
||||
@@ -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}"""
|
||||
}
|
||||
+54
-15
@@ -1,29 +1,68 @@
|
||||
# Run the System File Checker to scan for and repair any problems with protected system files
|
||||
LogMsg "Running System File Checker"
|
||||
$SFCOutput = $(sfc /scannow) | Out-String
|
||||
LogMsg $SFCOutput
|
||||
# Run SFC to check the filesystem, convert the result from Unicode to UTF8, delete the intermediate $tempFile
|
||||
$tempFile = New-TemporaryFile
|
||||
$(sfc.exe /scannow) | Out-File $tempFile
|
||||
Set-Content -Path $tempFile -Value $(Get-Content -Path $tempFile -Encoding Unicode) -Encoding UTF8
|
||||
$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
|
||||
|
||||
# Examine $SFCOutput for integrity violations, if found make sure chkdsk runs at the next boot
|
||||
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
|
||||
If ( IsWindowsVersion -ge "6.2" )
|
||||
{
|
||||
$BeginDISMRun = $(Get-Date).AddSeconds(-1)
|
||||
$DISMRun = $false
|
||||
If ( IsWindowsVersion -ge "6.2" ) {
|
||||
|
||||
LogMsg "Beginning repair of Windows Component Store"
|
||||
# Record start of DISM repair
|
||||
$BeginDISMRun = (Get-Date)
|
||||
|
||||
Try {
|
||||
Write-Output "Beginning repair of Windows Component Store"
|
||||
Start-Process "dism.exe" -ArgumentList "/online /cleanup-image /restorehealth" -Wait
|
||||
|
||||
LogMsg "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
|
||||
|
||||
$EndDISMRun = $(Get-Date).AddSeconds(1)
|
||||
$DISMRun = $true
|
||||
}
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
# Adding results of DISM log file to this log file
|
||||
LogMsg "Beginning Windows Component Store log data collection"
|
||||
# Record end of DISM repair
|
||||
$EndDISMRun = (Get-Date)
|
||||
|
||||
# Extract results of DISM repair from system log and add them to this log
|
||||
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})' |
|
||||
ForEach-Object {
|
||||
If ( ((Get-Date $_.Line.Substring(0,19)) -gt $BeginDISMRun) -and ((Get-Date $_.Line.Substring(0,19)) -le $EndDISMRun) ) {
|
||||
LogMsg $_.Line
|
||||
Write-Output $_.Line
|
||||
}
|
||||
}
|
||||
LogMsg "Completed Windows Component Store log data collection"
|
||||
Write-Output "Completed Windows Component Store log data collection"
|
||||
}
|
||||
Else { LogMsg "Online image cleanup and restoration is only supported on Windows 8 and higher" }
|
||||
Else { Write-Output "Online image cleanup and restoration is only supported on Windows 8 and higher" }
|
||||
|
||||
# Schedule Check Disk if needed
|
||||
If ( $ScheduleCheckDisk ) {
|
||||
Write-Output "Scheduling disk repair at next boot"
|
||||
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,132 @@
|
||||
<#
|
||||
Title: Fix-WindowsManagementInsturmentationWMI
|
||||
Copyright (c) Emberkom LLC
|
||||
Author: David Yoder
|
||||
Date: 2023-03-14
|
||||
|
||||
Intent and Notes:
|
||||
This script will attempt to salvage the WMI repository. If that fails, it will
|
||||
rebuild the repository by recompiling all MOF and MFL files and re-registering
|
||||
all *.dll and *.exe files that exist in the WBEM directory.
|
||||
|
||||
This script will take ~7-10min on mid hardware, and >15min on old/slow hardware.
|
||||
|
||||
Microsoft documentation recommends using the following command to rebuild the
|
||||
WMI repository:
|
||||
winmgmt /resetrepository
|
||||
However, in my experience the above command doesn't always fix the issue.
|
||||
#>
|
||||
|
||||
# Exit the script if WMI does not appear to be installed at all
|
||||
If ( !(Test-Path "${Env:SystemRoot}\System32\wbem") ) { Write-Error "WMI components are not present on this device. Please install them manually." ; Exit 1 }
|
||||
|
||||
$WMIService = Get-Service -Name winmgmt -ErrorAction SilentlyContinue
|
||||
|
||||
# Exit the script if we cannot find the winmgmt service
|
||||
If ( !$WMIService ) { Write-Output "The winmgmt service cannot be found, WMI is likely not the problem on this endpoint, no actions will be performed by this script" }#; Exit 1 }
|
||||
|
||||
# Exit the script if the winmgmt service is not configured for automatic startup
|
||||
If ( $WMIService.StartType -ne 'Automatic' ) { Write-Output "The winmgmt service is not configured to automatically start, this is not a default configuration, no actions will be performed by this script" }#; Exit 1 }
|
||||
|
||||
# Exit the script if the winmgmt service is not running
|
||||
If ( $WMIService.Status -ne 'Running' ) { Write-Output "The winmgmt service is configured to start automatically but is not currently running, WMI is likely not the issue on this endpoint, no actions will be performed by this script" }#; Exit 1}
|
||||
|
||||
# Function to test if WMI is functioning correctly
|
||||
Function Test-WMIFunctionality {
|
||||
# Track the number of failed functionality tests
|
||||
$WMIErrorLevel = 0
|
||||
|
||||
# Specify the WMI class name to query
|
||||
$Class = 'Win32_OperatingSystem'
|
||||
|
||||
# Specify a property in the WMI class that we can test for a value
|
||||
$Property = 'LastBootUpTime'
|
||||
|
||||
# Test that our property has a value, and that the WBEM folder exists.
|
||||
If ( $null -eq $(Get-CimInstance -ClassName $Class -ErrorAction SilentlyContinue | Select-Object $Property -ErrorAction SilentlyContinue).$Property )
|
||||
{ Write-Error "Failed to query ${Class} for the value of ${Property}" ; $WMIErrorLevel++ }
|
||||
|
||||
# Test that the WMI repository is consistent
|
||||
$WMIRepoStatus = (winmgmt.exe /verifyrepository)
|
||||
If ( ($WMIRepoStatus -contains "inconsistent") -or ($WMIRepoStatus -contains "failed" ) )
|
||||
{ Write-Error "WMI repository is in an inconsistent state" ; $WMIErrorLevel++ }
|
||||
|
||||
If ( $WMIErrorLevel -eq 0 ) { Return $true } Else { Return $false }
|
||||
}
|
||||
|
||||
# Guard to exit script if no repair is necessary
|
||||
If ( Test-WMIFunctionality ) { Write-Output "WMI components are functioning correctly" ; Exit 0 }
|
||||
|
||||
# Record the start time of the repair
|
||||
$StartTime = (Get-Date)
|
||||
|
||||
# Attempt to salvage WMI repository and exit script if no further repair is necessary
|
||||
Write-Output "Attempting to salvage WMI repository"
|
||||
$WMIRepoSalvage = (winmgmt.exe /salvagerepository)
|
||||
If ( ($WMIRepoSalvage -notcontains "failed") -and (Test-WMIFunctionality) ) { Write-Output "WMI repository successfully salvaged" ; Exit 0 }
|
||||
|
||||
Try {
|
||||
Write-Output "Failed to salvage WMI repository, attempting manual reinitialization of WMI"
|
||||
|
||||
# Disable and stop the winmgmt service
|
||||
Write-Output "Stopping and disabling WMI service"
|
||||
Set-Service -Name winmgmt -StartupType Disabled
|
||||
Stop-Service -Name winmgmt -Force
|
||||
|
||||
# Set the WBEM directory for manual refresh of WMI components
|
||||
$TargetDir = "${Env:WinDir}\System32\wbem"
|
||||
|
||||
# Rename the Repository directory to Repo_backup
|
||||
If ( Test-Path "${TargetDir}\Repo_backup" ) { Remove-Item "${TargetDir}\Repo_backup" -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
If ( Test-Path "${TargetDir}\Repository" ) { Rename-Item -Path "${TargetDir}\Repository" -NewName "${TargetDir}\Repo_backup" -Force }
|
||||
|
||||
# Register all *.dll files
|
||||
Write-Output "Registering all DLL files in ""${TargetDir}"""
|
||||
$WMIDlls = Get-ChildItem -Path "${TargetDir}\*" -File -Include *.dll
|
||||
ForEach ( $dll in $WMIDlls ) {
|
||||
$path = $dll.FullName
|
||||
Write-Output " ""${path}"""
|
||||
Start-Process "regsvr32.exe" -ArgumentList "/s ""${path}""" -Wait
|
||||
}
|
||||
|
||||
|
||||
# Register all *.exe files except for:
|
||||
# wbemcntl.exe, wbemtest.exe, mofcomp.exe
|
||||
Write-Output "Registering all supported EXE files in ""${TargetDir}"""
|
||||
$WMIExes = Get-ChildItem -Path "${TargetDir}\*" -File -Include *.exe | Where-Object { ($_.BaseName.ToLower() -ne "wbemcntl") -and ($_.BaseName.ToLower() -ne "wbemtest") -and ($_.BaseName.ToLower() -ne "mofcomp") }
|
||||
ForEach ( $exe in $WMIExes ) {
|
||||
$path = $exe.FullName
|
||||
Write-Output " ""${path}"""
|
||||
Start-Process $path -ArgumentList "/regserver" -Wait
|
||||
}
|
||||
|
||||
# Register all *.mof and *.mfl files
|
||||
Write-Output "Compiling all MOF and MFL files in ""${TargetDir}"""
|
||||
$WMIMofs = Get-ChildItem -Path "${TargetDir}\*" -File -Include *.mof,*.mfl
|
||||
ForEach ( $mof in $WMIMofs ) {
|
||||
$path = $mof.FullName
|
||||
Write-Output " ""${path}"""
|
||||
Start-Process "${TargetDir}\mofcomp.exe" -ArgumentList """${path}""" -Wait
|
||||
}
|
||||
}
|
||||
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
Finally {
|
||||
# Start the winmgmt service
|
||||
Write-Output "Enabling and starting WMI service"
|
||||
Set-Service -Name winmgmt -StartupType Automatic
|
||||
Start-Service -Name winmgmt
|
||||
}
|
||||
|
||||
# Final test of WMI
|
||||
If ( !(Test-WMIFunctionality) ) { Write-Output "WMI repair failed. Please restart this device and run this script again" ; Exit 1 }
|
||||
|
||||
# Remove Repo_backup directory
|
||||
If ( Test-Path "${TargetDir}\Repo_backup" ) {
|
||||
Write-Output "Deleting ""${TargetDir}\Repo_backup"""
|
||||
Remove-Item -Path "${TargetDir}\Repo_backup" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
$ElapsedTime = (New-TimeSpan -Start $StartTime -End (Get-Date)).ToString("hh\:mm\:ss")
|
||||
Write-Output "WMI has been successfully repaired! Time elapsed: ${ElapsedTime}"
|
||||
@@ -1,4 +1,4 @@
|
||||
LogMsg "Fix-WindowsScriptingComponents.ps1"
|
||||
Write-Output "Fix-WindowsScriptingComponents.ps1"
|
||||
|
||||
## List of all scripting component DLL files
|
||||
$DLL_FILES = @("${Env:WINDIR}\system32\vbscript.dll",
|
||||
@@ -22,9 +22,9 @@ ForEach ($dll in $DLL_FILES)
|
||||
If ( Test-Path $dll )
|
||||
{
|
||||
If ( $dll -Match "syswow64" ) { $REGSVR32 = "${Env:WINDIR}\syswow64\regsvr32" } Else { $REGSVR32 = "regsvr32" }
|
||||
LogMsg "Unregister: ${dll}"
|
||||
Write-Output "Unregister: ${dll}"
|
||||
Try { Start-Process "${REGSVR32}" -ArgumentList "/u /s ${dll}" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ ForEach ($dll in $DLL_FILES)
|
||||
{
|
||||
If ( $dll -Match "syswow64" )
|
||||
{ $REGSVR32 = "${Env:WINDIR}\syswow64\regsvr32" } Else { $REGSVR32 = "regsvr32" }
|
||||
LogMsg "Register: ${dll}"
|
||||
Write-Output "Register: ${dll}"
|
||||
Try { Start-Process "${REGSVR32}" -ArgumentList "/s ${dll}" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
}
|
||||
@@ -5,18 +5,18 @@ $TARGET_DIR = "${Env:WinDir}\SoftwareDistribution"
|
||||
$DEST_DIR = "${Env:WinDir}\SoftwareDistribution.old"
|
||||
|
||||
## Delete any existing folder from a previous execution of this script
|
||||
LogMsg "Deleting ""${DEST_DIR}"""
|
||||
Write-Output "Deleting ""${DEST_DIR}"""
|
||||
Try { Remove-Item $DEST_DIR -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
## Stop Background Intelligent Transfer Services and Windows Update
|
||||
Control-Service -Stop -Name BITS
|
||||
Control-Service -Stop -Name wuauserv
|
||||
|
||||
## Rename the SoftwareDistribution folder, forcing Windows Update to recreate all metrics and redownload all updates
|
||||
LogMsg "Renaming ""${TARGET_DIR}"" to ""${DEST_DIR}"""
|
||||
Write-Output "Renaming ""${TARGET_DIR}"" to ""${DEST_DIR}"""
|
||||
Try { Rename-Item -Path $TARGET_DIR -NewName $DEST_DIR -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
## Start Windows Update and Background Intelligent Transfer Services
|
||||
Control-Service -Start -Name BITS
|
||||
@@ -25,11 +25,11 @@ Control-Service -Start -Name wuauserv
|
||||
## Delete SoftwareDistribution.old from this execution of this script
|
||||
## Comment this section out if you want to leave the renamed folder there for some reason,
|
||||
## but keep in mind this folder can be very large and takes up a lot of space.
|
||||
LogMsg "Deleting ""${DEST_DIR}"""
|
||||
Write-Output "Deleting ""${DEST_DIR}"""
|
||||
Try { Remove-Item $DEST_DIR -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
## Force the Windows Update client to check for new updates
|
||||
LogMsg "Resetting WSUS authorization and starting update detection"
|
||||
Write-Output "Resetting WSUS authorization and starting update detection"
|
||||
Try { Start-Process "${Env:WinDir}\System32\wuauctl.exe" -ArgumentList "/resetauthorization /detectnow" }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
# This script depends on the host, port, username, and password being defined in the calling script
|
||||
If ( -not($SFTPHost) ) { Write-Error "SFTP host is not defined, this script will exit." ; Exit }
|
||||
If ( -not($SFTPPort) ) { Write-Error "SFTP port is not defined, this script will exit." ; Exit }
|
||||
If ( -not($SFTPUser) ) { Write-Error "SFTP username is not defined, this script will exit." ; Exit }
|
||||
If ( -not($SFTPPass) ) { Write-Error "SFTP password is not defined, this script will exit." ; Exit }
|
||||
|
||||
$ExportedEvents = "${Env:TEMP}\${Env:COMPUTERNAME}_events.csv"
|
||||
|
||||
# Install Posh-SSH module
|
||||
If ( !(Get-Module -ListAvailable -Name Posh-SSH) ) {
|
||||
Write-Output "Installing Posh-SSH module"
|
||||
Install-Module -Name Posh-SSH -Force
|
||||
}
|
||||
|
||||
# Cleanup previous runs
|
||||
If ( Test-Path $ExportedEvents ) {
|
||||
Write-Output "Deleting exported events from previous script run ""${ExportedEvents}"""
|
||||
Try { Remove-Item $ExportedEvents -Force -ErrorAction Stop }
|
||||
Catch { Write-Output "Cannot remove files from previous script execution. This script cannot run." ; Exit }
|
||||
}
|
||||
|
||||
# Get all available log files
|
||||
$Logs = Get-WinEvent -ListLog *
|
||||
|
||||
# Export all significant events from all available log files
|
||||
ForEach ($Log in $Logs) {
|
||||
$LogName = $Log.LogName
|
||||
Write-Output "Exporting events from: ${LogName}"
|
||||
Try { Get-WinEvent -LogName $LogName -FilterXPath "Event/System[Level=1 or Level=2 or Level=3]" -ErrorAction Stop | Select-Object LogName,ProviderName,Level,Id,Message | Export-Csv -Path "${ExportedEvents}" -Append -NoTypeInformation -ErrorAction Stop }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
# Deduplicate exported events
|
||||
Write-Output "Deduplicating and sorting all exported events"
|
||||
Try {
|
||||
$TempEvents = Import-Csv $ExportedEvents -ErrorAction Stop | Sort-Object LogName,ProviderName,Id -Unique
|
||||
$TempEvents | Sort-Object -Property LogName,ProviderName | Export-Csv $ExportedEvents -NoTypeInformation -ErrorAction Stop
|
||||
}
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
# Upload to SFTP site
|
||||
If ( Test-Path $ExportedEvents ) {
|
||||
$SFTPCred = New-Object System.Management.Automation.PSCredential($SFTPUser,$SFTPPass)
|
||||
|
||||
# Wait for a random amount of time before starting the connection and uploading
|
||||
Start-Sleep -Seconds $(Get-Random -Minimum 1 -Maximum 120)
|
||||
|
||||
# Make the connection and upload the file
|
||||
$SFTPSession = New-SFTPSession -ComputerName $SFTPHost -Credential $SFTPCred -AcceptKey -Port $SFTPPort
|
||||
Write-Output "Uploading events to SFTP site"
|
||||
Try { Set-SFTPItem -SessionId $SFTPSession.SessionId -Path $ExportedEvents -Destination . -Force }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
Finally { Remove-SFTPSession -SessionId $SFTPSession.SessionId }
|
||||
|
||||
}
|
||||
|
||||
# Cleanup
|
||||
Write-Output "Deleting ""${ExportedEvents}"""
|
||||
Remove-Item $ExportedEvents -Force -ErrorAction SilentlyContinue
|
||||
@@ -9,12 +9,12 @@ If ( Test-Path $FILE ) { Clear-Content -Path $FILE -Force }
|
||||
Function Write-LineToFile {
|
||||
param([Parameter(Mandatory=$true)][string]$Path,[Parameter(Mandatory=$true)][string]$Line)
|
||||
If ( -not (Test-Path $Path) ) {
|
||||
LogMsg "Creating file ""${Path}"""
|
||||
Write-Output "Creating file ""${Path}"""
|
||||
Try { New-Item -Path $Path -ItemType File -Force | Out-Null }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
Try { Add-Content -Path $Path -Value $Line }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
## Function to test whether or not an item has a specified property
|
||||
@@ -22,7 +22,7 @@ Function Test-ItemProperty {
|
||||
param([Parameter(Mandatory=$true)][string]$Path,[Parameter(Mandatory=$true)][string]$Name)
|
||||
Try {
|
||||
$test = Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue
|
||||
If (($test -eq $null) -or ($test.Length -eq 0)) { Return $false } Else { Return $true }
|
||||
If (($null -eq $test) -or ($test.Length -eq 0)) { Return $false } Else { Return $true }
|
||||
}
|
||||
Catch { Return $false }
|
||||
}
|
||||
@@ -59,7 +59,7 @@ Function Get-ItemPropertyInfo {
|
||||
|
||||
## Make sure $formatstring has a value, if not just try to cast directly to a datetime
|
||||
Try {
|
||||
If ( $formatstring -eq $null ) {
|
||||
If ( $null -eq $formatstring ) {
|
||||
$date = [datetime]$retval
|
||||
} Else {
|
||||
$date = [datetime]::ParseExact($retval,$formatstring,$null)
|
||||
@@ -68,7 +68,7 @@ Function Get-ItemPropertyInfo {
|
||||
## Set $retval to a standard datetime format
|
||||
$retval = $date.ToString('yyyy-MM-dd')
|
||||
}
|
||||
Catch { LogErr $_.Exception.Message; $retval = $origRetval }
|
||||
Catch { Write-Error $_.Exception.Message; $retval = $origRetval }
|
||||
}
|
||||
Default {}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
@@ -8,13 +8,13 @@ If (! (Test-Path "${Env:ProgramFiles}\4KDownload\4kvideodownloader\4kvideodownlo
|
||||
$INSTALLER = Download-File -URL $URL
|
||||
|
||||
## Install the app
|
||||
LogMsg "Installing 4K Video Downloader from ""${INSTALLER}"""
|
||||
Write-Output "Installing 4K Video Downloader from ""${INSTALLER}"""
|
||||
Try { Start-Process "msiexec.exe" -ArgumentList "/i ${INSTALLER} /qn /norestart" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
## Delete the installer
|
||||
LogMsg "Deleting downloaded installer from ""${INSTALLER}"""
|
||||
Write-Output "Deleting downloaded installer from ""${INSTALLER}"""
|
||||
Try { Remove-Item -Path $INSTALLER -Force -ErrorAction SilentlyContinue }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
Else { LogMsg "4K Video Downloader is already installed!" }
|
||||
Else { Write-Output "4K Video Downloader is already installed!" }
|
||||
@@ -3,8 +3,8 @@ $URL = 'https://download.adobe.com/pub/connect/updaters/meeting/11_0/ConnectApp1
|
||||
|
||||
## Install Adobe Connect
|
||||
If ( IsURLValid $URL ) {
|
||||
LogMsg "Installing Adobe Connect from ${URL}"
|
||||
Write-Output "Installing Adobe Connect from ${URL}"
|
||||
Try { Start-Process "msiexec.exe" -ArgumentList "/i ${URL} /qn /norestart" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
Else { LogMsg "URL is not valid: ${URL}" }
|
||||
Else { Write-Output "URL is not valid: ${URL}" }
|
||||
@@ -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 (GetTempPath), $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 ; LogMsg "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 ) {
|
||||
LogMsg "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)
|
||||
LogMsg "AccountID : ${ACCOUNT_ID}"
|
||||
LogMsg "AgentID : ${AGENT_ID}"
|
||||
LogMsg "CustomerID : ${CUSTOMER_ID}"
|
||||
LogMsg "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 { LogErr $_.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
|
||||
LogMsg "Installing Atera agent from ""${AGENT_INSTALLER}"""
|
||||
Try { Start-Process "msiexec.exe" -ArgumentList "/i ""${AGENT_INSTALLER}"" /qn /norestart" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
|
||||
# Delete the agent installer
|
||||
LogMsg "Deleting ""${AGENT_INSTALLER}"""
|
||||
Try { Remove-Item $AGENT_INSTALLER -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
|
||||
If ( $PreviousInstall -and $ACCOUNT_ID -and $AGENT_ID -and $CUSTOMER_ID) {
|
||||
LogMsg "Restoring agent information from previous install"
|
||||
LogMsg " - Stopping AteraAgent service"
|
||||
Stop-Service -Name 'AteraAgent' -Force
|
||||
LogMsg " - 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
|
||||
LogMsg " - Starting AteraAgent service"
|
||||
Start-Service -Name 'AteraAgent'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Delete this script
|
||||
Remove-Item $MyInvocation.MyCommand.Path -Force
|
||||
@@ -1,15 +1,11 @@
|
||||
## Define URL for downloading the installer
|
||||
$URL = 'https://efulfillment.autodesk.com/NetSWDLD/2020/DTCONN/2FF3C1A4-14EC-3C6D-B127-47327638CC14/SFX/Autodesk_Desktop_Connector_15_5_0_1684_Win_64bit.sfx.exe'
|
||||
# Define URL for downloading the installer
|
||||
$URL = Get-AbsoluteURI -URL 'https://www.autodesk.com/adsk-connect-64?'
|
||||
|
||||
## Make sure the URL is valid before we do anything
|
||||
If ( IsURLValid $URL )
|
||||
{
|
||||
## Set the local file name to download to
|
||||
$FILE = '{0}{1}' -f (GetTempPath),(Split-Path $URL -Leaf)
|
||||
# Set the local file name to download to
|
||||
$FILE = '{0}{1}' -f (Get-TempPath),(Split-Path $URL -Leaf)
|
||||
|
||||
## Download installer
|
||||
Download-File -URL $URL -File $FILE
|
||||
# Download installer
|
||||
Download-File -URL $URL -File $FILE
|
||||
|
||||
## Install package
|
||||
Start-Process $FILE
|
||||
}
|
||||
# Install package
|
||||
Start-Process $FILE
|
||||
@@ -1,7 +1,7 @@
|
||||
$SHORTCUT_PATH = "${Env:Public}\Desktop\Deltek Vision.lnk"
|
||||
|
||||
# Remove the shortcut if it already exists
|
||||
If ( Test-Path $SHORTCUT_PATH ) { LogMsg "Removing existing shortcut: ${SHORTCUT_PATH}" ; Remove-Item $SHORTCUT_PATH }
|
||||
If ( Test-Path $SHORTCUT_PATH ) { Write-Output "Removing existing shortcut: ${SHORTCUT_PATH}" ; Remove-Item $SHORTCUT_PATH }
|
||||
|
||||
# If the shortcut doesn't exist
|
||||
If ( -not (Test-Path $SHORTCUT_PATH) ) {
|
||||
@@ -28,10 +28,10 @@ If ( -not (Test-Path $SHORTCUT_PATH) ) {
|
||||
## Create the shortcut
|
||||
If ( $BROWSER_PATH ) {
|
||||
|
||||
LogMsg "Installing Deltek Vision for ${APP_NAME}"
|
||||
Create-Shortcut -Path "${Env:Public}\Desktop\Deltek Vision.lnk" -TargetPath $BROWSER_PATH -Arguments $VisionURL -Description "Launch Deltek Vision"
|
||||
Write-Output "Installing Deltek Vision for ${APP_NAME}"
|
||||
New-Shortcut -Path "${Env:Public}\Desktop\Deltek Vision.lnk" -TargetPath $BROWSER_PATH -Arguments $VisionURL -Description "Launch Deltek Vision"
|
||||
}
|
||||
Else { LogMsg "A compatible browser could not be found: ${BROWSER_PATH}" }
|
||||
Else { Write-Output "A compatible browser could not be found: ${BROWSER_PATH}" }
|
||||
}
|
||||
Else { LogMsg "No URL to Deltek Vision was specified!" }
|
||||
Else { Write-Output "No URL to Deltek Vision was specified!" }
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ ElseIf ( (IsWindowsVersion -ge "6.1") -and (IsWindowsVersion -lt "10.0") )
|
||||
If ( Is64bit ) { $URL = '{0}/{1}' -f $BaseURL, 'DisplayLink_Win7-8.1_x64.msi' }
|
||||
Else { $URL = '{0}/{1}' -f $BaseURL, 'DisplayLink_Win7-8.1_x86.msi' }
|
||||
}
|
||||
Else { LogMsg "Operating system is not supported" ; Exit }
|
||||
Else { Write-Output "Operating system is not supported" ; Exit }
|
||||
|
||||
## Install DisplayLink
|
||||
Install-Program -Path "msiexec.exe" -Arguments "/i ${URL} /qn /norestart"
|
||||
|
||||
@@ -1,27 +1,51 @@
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$URL
|
||||
)
|
||||
# NOTE: The $CONFIG variable must be set by the calling script
|
||||
|
||||
# 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
|
||||
# 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
|
||||
$TEMPDIR = '{0}eset-protect-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 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 ; Return }
|
||||
|
||||
# Get bitness of endpoint and set $AGENTNAME
|
||||
If ( Is64bit ) { $AGENTNAME = "agent_x64.msi" } Else { $AGENTNAME = "agent_x86.msi" }
|
||||
|
||||
# Download the installer
|
||||
$INSTALLER = '{0}epi_win_live_installer.exe' -f (GetTempPath)
|
||||
LogMsg "Downloading ESET Management Agent live installer"
|
||||
Try { Invoke-WebRequest -Uri $URL -UseBasicParsing -OutFile $INSTALLER }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
$INSTALLER = "${TEMPDIR}\${AGENTNAME}"
|
||||
$URL = 'https://download.eset.com/com/eset/apps/business/era/agent/latest/{0}' -f $AGENTNAME
|
||||
Write-Output "Downloading ESET Management Agent"
|
||||
Download-File -URL $URL -File $INSTALLER
|
||||
|
||||
# Install the app
|
||||
LogMsg "Installing ESET Management Agent from ""${INSTALLER}"""
|
||||
Try { Start-Process "${INSTALLER}" -ArgumentList "--silent --accepteula" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Write-Output "Installing ESET Management Agent from ""${INSTALLER}"""
|
||||
Try { Start-Process "msiexec.exe" -ArgumentList "/i ${INSTALLER} /qn /norestart /log installer.log" -Wait }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
# Delete the installer
|
||||
LogMsg "Deleting downloaded installer ""${INSTALLER}"""
|
||||
Try { Remove-Item -Path $INSTALLER -Force -ErrorAction SilentlyContinue }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
|
||||
# Delete this script
|
||||
Remove-Item $MyInvocation.MyCommand.Path -Force
|
||||
Write-Output "Deleting temp directory and its contents"
|
||||
Try { Remove-Item -Path $TEMPDIR -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
## This script requires the $Edition variable is set from the RMM script
|
||||
If (! $Edition ) { $Edition = 'desktop' }
|
||||
# Specify URL to "Backup for Windows" agent installer
|
||||
$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.7.0.107_ALLEDITIONS_Setup.exe'
|
||||
# Specify URL to "Backup Virtual Machine Edition" agent installer
|
||||
$ECB_VMM_URL = 'https://console.msp360.com/api/build/download?urlParams=OEFsMzhldm1SbjhaSDhBc2VtYkpUREM3Y2pJSzg0REU4dkVtR1pHREkxMGFFUk9qZXJnSXk5SUF0ei8wZlZOWUpYM2xDY3ErMkpOVGpuWTRISDNFcFRFdWRibDgvTm8vUDhpWndON3kzaHNwOTFVa3h6WUdoeVVod1JmT2lRQ1FlVTE3Y0pnUncwSEJwS3FFcDBmTWt3cEdnSy9YemRsSTRiQWJHNi96cUtWbzZsc25QVllaR1QyQ21VVS95dFFVTkNndXdPVStGQXBIT0FZb2FIUzFuZy9mMU5qZUd5emhab1hFYjZWODB4ND0*&brandId=fb8308e2-448a-4645-a5f6-a3632a7e57f4'
|
||||
|
||||
## 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.7.0.107_VMWARE_Setup.exe'
|
||||
|
||||
## Set the correct download URL and agent edition
|
||||
# Set the correct download URL and agent edition
|
||||
switch ( $Edition.ToLower() ) {
|
||||
'server' { $URL = $ECB_WIN_URL; $AgentEdition = 'baremetal' }
|
||||
'desktop' { $URL = $ECB_WIN_URL; $AgentEdition = 'desktop' }
|
||||
@@ -15,36 +12,67 @@ switch ( $Edition.ToLower() ) {
|
||||
default { $URL = $ECB_WIN_URL; $AgentEdition = 'desktop' }
|
||||
}
|
||||
|
||||
## Install and import additional Powershell modules
|
||||
Install-MSP360Module
|
||||
Import-Module -Name MSP360
|
||||
Install-PSAteraModule
|
||||
Import-Module -Name PSAtera
|
||||
# Install and import additional Powershell module
|
||||
Import-MSP360Module
|
||||
|
||||
## Install the agent
|
||||
LogMsg "Installing Emberkom Cloud Backup agent"
|
||||
Try { Install-MBSAgent -URL $URL -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
|
||||
## Add backup user to installed product
|
||||
Try {
|
||||
LogMsg "Getting Atera agent and custom field information"
|
||||
Set-AteraAPIKey -APIKey $AteraAPIKey
|
||||
$AteraAgent = Get-AteraAgent
|
||||
$MSP360Username = $(Get-AteraCustomValue -ObjectType Customer -ObjectId $AteraAgent.CustomerID -FieldName 'MSP360 Account Username').ValueAsString
|
||||
$MSP360Password = $(Get-AteraCustomValue -ObjectType Customer -ObjectId $AteraAgent.CustomerID -FieldName 'MSP360 Account Password').ValueAsString
|
||||
$MSP360Password = ConvertTo-SecureString -string $MSP360Password -AsPlainText -Force
|
||||
LogMsg "Adding backup user account: ${MSP360Username}"
|
||||
Add-MBSUserAccount -User $MSP360Username -Password $MSP360Password
|
||||
# Exit this script if the agent is already installed
|
||||
If ( $(Get-MBSAgent -ErrorAction SilentlyContinue) ) {
|
||||
Microsoft.Powershell.Utility\Write-Output "Backup agent is already installed. This script will now exit."
|
||||
Return
|
||||
}
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
|
||||
## Set the agent edition
|
||||
## Note: the product edition must be set *after* the user is added
|
||||
LogMsg "Setting Emberkom Cloud Backup agent edition to ""$AgentEdition"""
|
||||
Try { Set-MBSAgentSetting -Edition $AgentEdition -Verbose }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
#
|
||||
# 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
|
||||
#
|
||||
|
||||
## Delete the desktop icon
|
||||
"${Env:PUBLIC}\Desktop\Emberkom Backup.LNK",
|
||||
"${Env:PUBLIC}\Desktop\Emberkom Cloud Backup.LNK" | Remove-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
|
||||
Write-Output "Installing Emberkom Cloud Backup agent"
|
||||
Try { Start-Process "${Installer}" -ArgumentList "/S" -Wait }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
# Add backup user to installed product
|
||||
Write-Output "Adding backup user account: ${MSP360Username}"
|
||||
Try { Add-MBSUserAccount -User $MSP360Username -Password $(ConvertTo-SecureString -string $MSP360Password -AsPlainText -Force) }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
# Wait a few seconds to let the agent check-in
|
||||
Start-Sleep -Seconds 10
|
||||
|
||||
# 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"""
|
||||
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 }
|
||||
|
||||
# Delete $Installer
|
||||
If ( Test-Path $Installer ) { Write-Output "Deleting downloaded file: ${Installer}" ; Remove-Item $Installer -Force -ErrorAction SilentlyContinue }
|
||||
|
||||
# Delete the desktop icon
|
||||
If ( Test-Path "${Env:PUBLIC}\Desktop\Emberkom Backup.LNK" ) { Remove-Item -Path "${Env:PUBLIC}\Desktop\Emberkom Backup.LNK" -Force -ErrorAction SilentlyContinue }
|
||||
If ( Test-Path "${Env:PUBLIC}\Desktop\Emberkom Cloud Backup.LNK" ) { Remove-Item -Path "${Env:PUBLIC}\Desktop\Emberkom Cloud Backup.LNK" -Force -ErrorAction SilentlyContinue }
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# Exit early if apps are running
|
||||
$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 }
|
||||
|
||||
# Path to the MSI installer
|
||||
$URL = 'https://enscape-installer.chaosgroup.com/installer.enscape.io/Enscape-4.0.2.11.exe'
|
||||
|
||||
# Set path to temp directory
|
||||
$TEMPDIR = '{0}enscape-installer' -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
|
||||
$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 }
|
||||
@@ -1,46 +1,37 @@
|
||||
## Path to the MSI installer
|
||||
$MSIURL = 'https://lfupdate.s3.amazonaws.com/clients/outlook/admin_msi/LiquidFiles_Admin_2.0.132.msi'
|
||||
# Path to the MSI installer
|
||||
$MSIURL = 'https://lfupdate.s3.amazonaws.com/clients/outlook/admin_msi/LiquidFiles_OutlookPlugin_Admin_3.0.37.msi'
|
||||
|
||||
Install-PSAteraModule
|
||||
Import-Module -Name PSAtera
|
||||
|
||||
## Get LF host URL from Atera
|
||||
LogMsg "Getting LiquidFiles host URL from Atera API"
|
||||
Try {
|
||||
Set-AteraAPIKey -APIKey $AteraAPIKey
|
||||
$AteraAgent = Get-AteraAgent
|
||||
$LFHost = $(Get-AteraCustomValue -ObjectType Customer -ObjectId $AteraAgent.CustomerID -FieldName 'LiquidFiles Host URL').ValueAsString
|
||||
}
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
|
||||
## Set installer args
|
||||
# Set installer args
|
||||
If (-not [string]::IsNullOrEmpty($LFHost) ) {
|
||||
LogMsg "LiquidFiles target server: ${LFHost}"
|
||||
$INSTALLER_ARGS = "REGKEYSCRIPT=""BaseUrl=${LFHost};"""
|
||||
Write-Output "LiquidFiles target server: ${LFHost}"
|
||||
$INSTALLER_ARGS = "REGKEYSCRIPT=""BaseUrl=${LFHost};InjectPositionDefault=3"""
|
||||
}
|
||||
Else { $INSTALLER_ARGS = '' }
|
||||
|
||||
## Stop any running LiquidFilesWindowsAgent process
|
||||
# Stop any running LiquidFilesWindowsAgent process
|
||||
$RunningProcess = Get-Process -Name "LiquidFilesWindowsAgent"
|
||||
If ($RunningProcess) {
|
||||
LogMsg "Stopping the LiquidFiles agent"
|
||||
Write-Output "Stopping the LiquidFiles agent"
|
||||
$RunningProcess | Stop-Process -Force
|
||||
}
|
||||
|
||||
## Run the installer
|
||||
$INSTALLER = Download-File -URL $MSIURL
|
||||
LogMsg "Installing Liquid Files Outlook Agent from ""${INSTALLER}"""
|
||||
Try { Start-Process "msiexec.exe" -ArgumentList "/i ${INSTALLER} /qn /norestart ${INSTALLER_ARGS}" -Wait -ErrorAction Stop }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
# Uninstall existing LiquidFiles agent/plugin
|
||||
'LiquidFiles*' | Uninstall-MSI
|
||||
|
||||
## Delete installer
|
||||
# Run the installer
|
||||
$INSTALLER = Download-File -URL $MSIURL
|
||||
Write-Output "Installing Liquid Files Outlook Agent from ""${INSTALLER}"""
|
||||
Try { Start-Process "msiexec.exe" -ArgumentList "/i ${INSTALLER} /qn /norestart ${INSTALLER_ARGS}" -Wait -ErrorAction Stop }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
# Delete installer
|
||||
Try {
|
||||
LogMsg "Deleting downloaded installation package"
|
||||
Write-Output "Deleting downloaded installation package"
|
||||
Remove-Item $INSTALLER -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
## Start the agent
|
||||
# Start the agent
|
||||
$PathToAgentExe64 = "${Env:ProgramFiles(x86)}\LiquidFiles Windows Agent\LiquidFilesWindowsAgent.exe"
|
||||
$PathToAgentExe32 = "${Env:ProgramFiles}\LiquidFiles Windows Agent\LiquidFilesWindowsAgent.exe"
|
||||
If ( Test-Path $PathToAgentExe64 ) {
|
||||
@@ -49,7 +40,7 @@ If ( Test-Path $PathToAgentExe64 ) {
|
||||
$PathToAgentExe = $PathToAgentExe32
|
||||
}
|
||||
If ($PathToAgentExe) {
|
||||
LogMsg "Starting LiquidFiles agent"
|
||||
Write-Output "Starting LiquidFiles agent"
|
||||
Try { Start-Process $PathToAgentExe }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
@@ -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 (GetTempPath), $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 { LogMsg "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 }
|
||||
@@ -16,7 +16,7 @@ Install-Program -Path $(Download-File -URL $URL) -Arguments "/S" -Delete
|
||||
## Start the app after installation
|
||||
If ( Test-Path $InstallPath )
|
||||
{
|
||||
LogMsg "Starting Nextcloud"
|
||||
Write-Output "Starting Nextcloud"
|
||||
Try { Start-Process $InstallPath }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
+3
-3
@@ -19,9 +19,9 @@ If ( IsAdmin )
|
||||
Else { $Shortcut = "${Env:SYSTEMDRIVE}\Users\Public\Desktop\OpenVPN GUI.LNK" }
|
||||
If ( Test-Path $Shortcut )
|
||||
{
|
||||
LogMsg "Deleting desktop shortcut: ${Shortcut}"
|
||||
Write-Output "Deleting desktop shortcut: ${Shortcut}"
|
||||
Try { Remove-Item $Shortcut -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
}
|
||||
Else { LogMsg "Must be an administrator to install OpenVPN" }
|
||||
Else { Write-Output "Must be an administrator to install OpenVPN" }
|
||||
@@ -1,3 +1,5 @@
|
||||
If ( Test-Path "${Env:ProgramFiles}\simpleinout-desktop\Simple In Out.exe" ) { Write-Output "Simple In/Out is already installed." ; Exit }
|
||||
|
||||
# Specify the URL to download the app from
|
||||
$URL = 'https://downloads.simpleinout.com/desktop/Simple-In-Out.msi'
|
||||
|
||||
@@ -5,11 +7,11 @@ $URL = 'https://downloads.simpleinout.com/desktop/Simple-In-Out.msi'
|
||||
$INSTALLER = Download-File -URL $URL
|
||||
|
||||
# Install the app
|
||||
LogMsg "Installing latest version of Simple In/Out from ""${INSTALLER}"""
|
||||
Write-Output "Installing latest version of Simple In/Out from ""${INSTALLER}"""
|
||||
Try { Start-Process "msiexec.exe" -ArgumentList "/i ${INSTALLER} /qn /norestart ALLUSERS=1" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
# Delete the installer
|
||||
LogMsg "Deleting downloaded installer from ""${INSTALLER}"""
|
||||
Write-Output "Deleting downloaded installer from ""${INSTALLER}"""
|
||||
Try { Remove-Item -Path $INSTALLER -Force -ErrorAction SilentlyContinue }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
+14
-53
@@ -1,59 +1,20 @@
|
||||
## Make sure we have an administrative token
|
||||
If ( IsAdmin )
|
||||
{
|
||||
## Define URL for downloading the installer
|
||||
$URL = "https://www.sketchup.com/sketchup/${InstallVersion}/SketchUpPro-exe"
|
||||
|
||||
## Make sure the URL is valid before we do anything
|
||||
If ( IsURLValid $URL )
|
||||
{
|
||||
## Uninstall SketchUp 2016
|
||||
If ( Test-Path "${Env:ProgramFiles}\SketchUp\SketchUp 2016\SketchUp.exe" )
|
||||
{
|
||||
LogMsg "Uninstalling SketchUp 2016"
|
||||
Try { Start-Process "msiexec.exe" -ArgumentList '/X{D87EE6DC-32BA-4219-AC75-0A6FD54ED058} /qn /norestart' -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
}
|
||||
# This is the URL for retreving the latest version of the specified installer
|
||||
# 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
|
||||
# https://www.sketchup.com/sketchup/2023/SketchUpPro-exe
|
||||
|
||||
## Uninstall SketchUp 2017
|
||||
If ( Test-Path "${Env:ProgramFiles}\SketchUp\SketchUp 2017\SketchUp.exe" )
|
||||
{
|
||||
LogMsg "Uninstalling SketchUp 2017"
|
||||
Try { Start-Process "msiexec.exe" -ArgumentList '/X{E59BD84C-169B-4F3F-AC5D-85127CF67051} /qn /norestart' -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
}
|
||||
# Define URL for downloading the installer
|
||||
$URL = 'https://download.sketchup.com/SketchUpStudio-2023-1-340-117.exe'
|
||||
|
||||
## Uninstall SketchUp 2019
|
||||
If ( Test-Path "${Env:ProgramFiles}\SketchUp\SketchUp 2019\SketchUp.exe" )
|
||||
{
|
||||
LogMsg "Uninstalling SketchUp 2019"
|
||||
Try { Start-Process "msiexec.exe" -ArgumentList '/X{06964675-EB01-6D18-6704-429DE73A8319} /qn /norestart' -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
}
|
||||
# TODO: Uninstall all versions of SketchUp
|
||||
#'SketchUp 2019', 'SketchUp 2020', 'SketchUp 2021', 'SketchUp 2022', 'SketchUp 2023', 'SketchUp Viewer' | Uninstall-MSI
|
||||
|
||||
## Uninstall SketchUp 2020
|
||||
If ( Test-Path "${Env:ProgramFiles}\SketchUp\SketchUp 2020\SketchUp.exe" )
|
||||
{
|
||||
LogMsg "Uninstalling SketchUp 2020"
|
||||
Try { Start-Process "msiexec.exe" -ArgumentList '/X{5778f9a3-781e-16f1-a6bf-08fd59dfa77b} /qn /norestart' -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
}
|
||||
# Set the local file name to download to
|
||||
$FILE = '{0}{1}' -f (Get-TempPath),(Split-Path $URL -Leaf)
|
||||
|
||||
## Uninstall SketchUp 2021
|
||||
If ( Test-Path "${Env:ProgramFiles}\SketchUp\SketchUp 2021\SketchUp.exe" )
|
||||
{
|
||||
LogMsg "Uninstalling SketchUp 2021"
|
||||
Try { Start-Process "msiexec.exe" -ArgumentList '/X{e31a06f0-fc24-f59f-5b2c-941521be9626} /qn /norestart' -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
}
|
||||
# Download installer
|
||||
$FILE = Download-File -URL $URL -File $FILE
|
||||
|
||||
## Set the local file name to download to
|
||||
$FILE = '{0}{1}.exe' -f (GetTempPath),(Split-Path $URL -Leaf)
|
||||
|
||||
## Download installer
|
||||
$FILE = Download-File -URL $URL -File $FILE
|
||||
|
||||
## Install package
|
||||
Install-Program -Path $FILE -Arguments "/silent" -Delete
|
||||
}
|
||||
}
|
||||
# Install package
|
||||
Install-Program -Path $FILE -Arguments "/silent" -Delete
|
||||
@@ -5,11 +5,11 @@ $URL = 'https://specsintact.ksc.nasa.gov/Software/downloads/SpecsIntact.msi'
|
||||
$INSTALLER = Download-File -URL $URL
|
||||
|
||||
## Install the app
|
||||
LogMsg "Installing latest version of SpecsIntact from ""${INSTALLER}"""
|
||||
Write-Output "Installing latest version of SpecsIntact from ""${INSTALLER}"""
|
||||
Try { Start-Process "msiexec.exe" -ArgumentList "/i ${INSTALLER} /qn /norestart" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
## Delete the installer
|
||||
LogMsg "Deleting downloaded installer from ""${INSTALLER}"""
|
||||
Write-Output "Deleting downloaded installer from ""${INSTALLER}"""
|
||||
Try { Remove-Item -Path $INSTALLER -Force -ErrorAction SilentlyContinue }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
@@ -0,0 +1,31 @@
|
||||
# This script is intended to be called from a Windows Provisioning Package.
|
||||
# The provisioning package invokes this script the following way:
|
||||
# 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'"
|
||||
|
||||
Function Install-SyncroAgent ([string]$URL) {
|
||||
|
||||
# Exit if $URL is null or empty
|
||||
If ([string]::IsNullOrEmpty($URL)) {
|
||||
Write-Output "The URL parameter cannot be null or empty"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
# Exit if running without admin rights
|
||||
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
|
||||
}
|
||||
|
||||
# Download and install the Syncro Agent
|
||||
$Installer = "${Env:TEMP}\SyncroInstaller.msi"
|
||||
Try { (New-Object System.Net.WebClient).DownloadFile($URL,$Installer) }
|
||||
Catch { Write-Error "The Syncro Agent installer could not be downloaded from ${URL}`n"+$_.Exception.Message ; Exit 1 }
|
||||
If ( Test-Path $Installer ) {
|
||||
Start-Process msiexec.exe -ArgumentList "/i ""${Installer}"" /qn /norestart" -Wait
|
||||
Remove-Item $Installer -Force -ErrorAction SilentlyContinue
|
||||
} Else {
|
||||
Write-Error "The Syncro Agent installer was successfully downloaded but could not be found at ""${Installer}"""
|
||||
Exit 1
|
||||
}
|
||||
Exit 0
|
||||
}
|
||||
+1
-1
@@ -10,4 +10,4 @@ If ( IsAdmin )
|
||||
|
||||
If ( $Installer ) { Install-Program -Path "${Installer}" -Arguments "/S" -Delete }
|
||||
}
|
||||
Else { LogMsg "Must be an administrator to install VLC" }
|
||||
Else { Write-Output "Must be an administrator to install VLC" }
|
||||
@@ -10,19 +10,19 @@ If ( IsAdmin )
|
||||
## Install package
|
||||
$Installer = Download-File -URL $URL
|
||||
Try { Start-Process "msiexec.exe" -ArgumentList "/i ${Installer} /qn /norestart DO_NOT_LAUNCH=1" -Wait -ErrorAction Stop }
|
||||
Catch { LogErr $_.Exception.Message ; Exit }
|
||||
Catch { Write-Error $_.Exception.Message ; Exit }
|
||||
|
||||
## Delete installer
|
||||
Try { If ( Test-Path $Installer ) { Remove-Item $Installer -Force } }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
## Install manager service
|
||||
Try { Start-Process "${Env:ProgramFiles}\WireGuard\wireguard.exe" -ArgumentList '/installmanagerservice' -Wait -ErrorAction SilentlyContinue }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
## Create shortcut on Public desktop
|
||||
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 { LogMsg "Must be an administrator to install WireGuard" }
|
||||
Else { Write-Output "Must be an administrator to install WireGuard" }
|
||||
+11
-11
@@ -20,7 +20,7 @@ If ( IsWindowsVersion -ge "6.1" )
|
||||
|
||||
## Check to make sure the ZeroTierOneService has started
|
||||
## We'll only loop through this for a few seconds before failing
|
||||
LogMsg "Attempting to start the ZeroTierOneService service..."
|
||||
Write-Output "Attempting to start the ZeroTierOneService service..."
|
||||
For ( $i = 1 ; $i -le 5 ; $i++ )
|
||||
{
|
||||
## Check to see if the service is running
|
||||
@@ -28,7 +28,7 @@ If ( IsWindowsVersion -ge "6.1" )
|
||||
{
|
||||
## Attempt to stop/start the service
|
||||
Try { Restart-Service -Name ZeroTierOneService -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
## Wait a few seconds before checking again
|
||||
Start-Sleep -Seconds 5
|
||||
@@ -37,7 +37,7 @@ If ( IsWindowsVersion -ge "6.1" )
|
||||
## If the service is running, break the loop
|
||||
Else
|
||||
{
|
||||
LogMsg " ZeroTierOneService service is started"
|
||||
Write-Output " ZeroTierOneService service is started"
|
||||
$ServiceRunning = $true ; break
|
||||
}
|
||||
}
|
||||
@@ -54,27 +54,27 @@ If ( IsWindowsVersion -ge "6.1" )
|
||||
If ( Test-Path "${ZTcli}" )
|
||||
{
|
||||
## Join the network, if $ZTcli exists
|
||||
LogMsg "Joining ZeroTier network ${JoinNetwork}"
|
||||
Write-Output "Joining ZeroTier network ${JoinNetwork}"
|
||||
Try { Start-Process $ZTcli -ArgumentList "join ${JoinNetwork}" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
Else { LogMsg "Could not find ""${ZTcli}""" }
|
||||
Else { Write-Output "Could not find ""${ZTcli}""" }
|
||||
}
|
||||
|
||||
## If the service still isn't running, just error out
|
||||
Else { LogMsg " ZeroTierOneService could not be started in a timely fashion" }
|
||||
Else { Write-Output " ZeroTierOneService could not be started in a timely fashion" }
|
||||
|
||||
}
|
||||
Else { LogMsg "A ZeroTier network ID was not specified, will not join any network" }
|
||||
Else { Write-Output "A ZeroTier network ID was not specified, will not join any network" }
|
||||
|
||||
## Remove the ZeroTier One Start Menu shortcut
|
||||
$ShortcutPath = "${ProgramData}\Microsoft\Windows\Start Menu\Programs\ZeroTier One.lnk"
|
||||
If ( Test-Path $ShortcutPath )
|
||||
{
|
||||
LogMsg "Deleting Start Menu shortcut from ""${ShortcutPath}"""
|
||||
Write-Output "Deleting Start Menu shortcut from ""${ShortcutPath}"""
|
||||
Try { Remove-Item $ShortcutPath -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
}
|
||||
Else { LogMsg "ZeroTier can only be installed on Windows 7 or higher operating systems" }
|
||||
Else { Write-Output "ZeroTier can only be installed on Windows 7 or higher operating systems" }
|
||||
|
||||
@@ -1,9 +1,67 @@
|
||||
# Management Scripts
|
||||
|
||||
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:
|
||||
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.
|
||||
|
||||
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."
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ End-Process -Name "DeltekVision" -Force
|
||||
$APP_PATH = "${Env:UserProfile}\AppData\Local\Apps\2.0"
|
||||
If ( Test-Path $APP_PATH ) {
|
||||
## Delete the existing installed app
|
||||
LogMsg "Deleting ""${APP_PATH}"""
|
||||
Write-Output "Deleting ""${APP_PATH}"""
|
||||
Try { Remove-Item $APP_PATH -Recurse -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
} Else { LogMsg "The path specified does not exist: ${APP_PATH}" }
|
||||
} Else { Write-Output "The path specified does not exist: ${APP_PATH}" }
|
||||
@@ -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 }
|
||||
}
|
||||
+6
-6
@@ -3,12 +3,12 @@
|
||||
|
||||
If ( $OlderThanDays -is [int] ) {
|
||||
If ( Test-Path $Path ) {
|
||||
LogMsg "Deleting all items older than ${OlderThanDays} days in ""${Path}"""
|
||||
$files = Get-ChildItem $Path | Where { !$_.PSIsContainer -and $_.LastWriteTime -lt (Get-Date).AddDays(-${OlderThanDays}) }
|
||||
Write-Output "Deleting all items older than ${OlderThanDays} days in ""${Path}"""
|
||||
$files = Get-ChildItem $Path | Where-Object { !$_.PSIsContainer -and $_.LastWriteTime -lt (Get-Date).AddDays(-${OlderThanDays}) }
|
||||
Foreach ( $f in $files ) {
|
||||
$msg = "Deleting {0}" -f $f.FullName ; LogMsg $msg
|
||||
$msg = "Deleting {0}" -f $f.FullName ; Write-Output $msg
|
||||
Try { Remove-Item $f -Force | Out-Null }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
} Else { LogMsg "Path does not exist: ""${Path}""" }
|
||||
} Else { LogMsg "The value entered for ""OlderThanDays"" is not an integer" }
|
||||
} Else { Write-Output "Path does not exist: ""${Path}""" }
|
||||
} Else { Write-Output "The value entered for ""OlderThanDays"" is not an integer" }
|
||||
@@ -21,7 +21,7 @@ ForEach ( $path in $RevitLocalPaths ) {
|
||||
|
||||
# Only remove Revit local files that have worksharing enabled
|
||||
If ( (Test-Path "${revitfile}") -and (Test-Path "${BackupDir}")) {
|
||||
LogMsg "Deleting ""${revitfile}"" and backup directory ""${BackupDir}"""
|
||||
Write-Output "Deleting ""${revitfile}"" and backup directory ""${BackupDir}"""
|
||||
Remove-Item "${revitfile}" -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item "${BackupDir}" -Recurse -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -13,34 +13,34 @@ If ( IsWindowsVersion -ge "10.0" ) {
|
||||
## Uninstall previous Windows 10 Update Assistant
|
||||
If ( Test-Path "${Env:SystemDrive}\Windows10Upgrade\Windows10UpgraderApp.exe" )
|
||||
{
|
||||
LogMsg "Uninstalling existing Windows 10 Update Assistant"
|
||||
Write-Output "Uninstalling existing Windows 10 Update Assistant"
|
||||
Try { Start-Process "${Env:SystemDrive}\Windows10Upgrade\Windows10UpgraderApp.exe" -ArgumentList '/ForceUninstall' -Wait }
|
||||
Catch { LogMsg $_.Exception.Message }
|
||||
Catch { Write-Output $_.Exception.Message }
|
||||
}
|
||||
|
||||
## Download Windows 10 Update Assistant
|
||||
$FILENAME = Split-Path $URL -leaf
|
||||
If ( Test-Path "${DOWNLOADS}\${FILENAME}" )
|
||||
{
|
||||
LogMsg "Deleting existing installer: ${DOWNLOADS}\${FILENAME}"
|
||||
Write-Output "Deleting existing installer: ${DOWNLOADS}\${FILENAME}"
|
||||
Try { Remove-Item "${DOWNLOADS}\${FILENAME}" -Force -ErrorAction SilentlyContinue }
|
||||
Catch { LogMsg $_.Exception.Message }
|
||||
Catch { Write-Output $_.Exception.Message }
|
||||
}
|
||||
LogMsg "Downloading ${URL} to ${DOWNLOADS}\${FILENAME}"
|
||||
Write-Output "Downloading ${URL} to ${DOWNLOADS}\${FILENAME}"
|
||||
Try { (New-Object System.Net.WebClient).DownloadFile($URL,"${DOWNLOADS}\${FILENAME}") }
|
||||
Catch { LogMsg $_.Exception.Message }
|
||||
Catch { Write-Output $_.Exception.Message }
|
||||
|
||||
## Install and run the Windows 10 Update Assistant
|
||||
If ( Test-Path "${DOWNLOADS}\${FILENAME}" )
|
||||
{
|
||||
LogMsg "Launching installer: ${DOWNLOADS}\${FILENAME}"
|
||||
Write-Output "Launching installer: ${DOWNLOADS}\${FILENAME}"
|
||||
Try { Start-Process -FilePath "${DOWNLOADS}\${FILENAME}" }
|
||||
Catch { LogMsg $_.Exception.Message }
|
||||
Catch { Write-Output $_.Exception.Message }
|
||||
}
|
||||
Else { LogMsg "${DOWNLOADS}\${FILENAME} does not exist" }
|
||||
Else { Write-Output "${DOWNLOADS}\${FILENAME} does not exist" }
|
||||
|
||||
}
|
||||
Else { LogMsg "This endpoint is already up to date." }
|
||||
Else { Write-Output "This endpoint is already up to date." }
|
||||
|
||||
}
|
||||
Else { LogMsg "This endpoint is not running Windows 10." }
|
||||
Else { Write-Output "This endpoint is not running Windows 10." }
|
||||
|
||||
@@ -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
|
||||
+16
-16
@@ -7,27 +7,27 @@ $ZIPFILE = Download-File -URL $URL
|
||||
## Setup the directory to extract the downloaded zip file to
|
||||
$WORKINGDIR = $ZIPFILE.Substring(0, $ZIPFILE.LastIndexOf('.'))
|
||||
If ( Test-Path $WORKINGDIR ) {
|
||||
LogMsg "Deleting existing directory: ""$WORKINGDIR"""
|
||||
Write-Output "Deleting existing directory: ""$WORKINGDIR"""
|
||||
Try { Remove-Item -Path $WORKINGDIR -Recurse -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
LogMsg "Creating new directory: ""$WORKINGDIR"""
|
||||
Write-Output "Creating new directory: ""$WORKINGDIR"""
|
||||
Try { New-Item -Path $WORKINGDIR -ItemType Directory -Force | Out-Null }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
## Extract $ZIPFILE to $WORKINGDIR
|
||||
Try {
|
||||
Add-Type -Assembly System.IO.Compression.FileSystem
|
||||
[IO.Compression.ZipFile]::ExtractToDirectory($ZIPFILE, $WORKINGDIR)
|
||||
}
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
## Run the SpeedTest CLI app
|
||||
$SPEEDTEST = "${WORKINGDIR}\speedtest.exe"
|
||||
If ( Test-Path $SPEEDTEST ) {
|
||||
Try {
|
||||
|
||||
LogMsg "Starting internet bandwidth test"
|
||||
Write-Output "Starting internet bandwidth test"
|
||||
$RESULTS = [string]( cmd.exe /c ""${SPEEDTEST} --accept-license --progress=no"" )
|
||||
|
||||
## Cleanup the output
|
||||
@@ -41,22 +41,22 @@ If ( Test-Path $SPEEDTEST ) {
|
||||
$RESULTS = $RESULTS -replace "Result URL: ", "`nResult URL: "
|
||||
$RESULTS = $RESULTS + "`n"
|
||||
} Else { $RESULTS = "The test did not product any output" }
|
||||
LogMsg $RESULTS
|
||||
LogMsg "Finished internet bandwidth test"
|
||||
Write-Output $RESULTS
|
||||
Write-Output "Finished internet bandwidth test"
|
||||
}
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
} Else { LogMsg "The file does not exist at the expected path: ""${SPEEDTEST}""" }
|
||||
} Else { Write-Output "The file does not exist at the expected path: ""${SPEEDTEST}""" }
|
||||
|
||||
## Cleanup files and directories created by this script
|
||||
If ( Test-Path $ZIPFILE ) {
|
||||
LogMsg "Deleting downloaded zip file: ""$ZIPFILE"""
|
||||
Write-Output "Deleting downloaded zip file: ""$ZIPFILE"""
|
||||
Try { Remove-Item -Path $ZIPFILE -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
} Else { LogMsg "Downloaded zip file no longer exists: ""$ZIPFILE""" }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
} Else { Write-Output "Downloaded zip file no longer exists: ""$ZIPFILE""" }
|
||||
|
||||
If ( Test-Path $WORKINGDIR ) {
|
||||
LogMsg "Deleting directory from extracted zip file: ""$WORKINGDIR"""
|
||||
Write-Output "Deleting directory from extracted zip file: ""$WORKINGDIR"""
|
||||
Try { Remove-Item -Path $WORKINGDIR -Recurse -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
} Else { LogMsg "Directory from extracted zip file no longer exists: ""$WORKINGDIR""" }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
} Else { Write-Output "Directory from extracted zip file no longer exists: ""$WORKINGDIR""" }
|
||||
@@ -0,0 +1,147 @@
|
||||
# Specify the working directory for this script to place files
|
||||
$WORK_DIR = "${Env:ProgramData}\Atera"
|
||||
|
||||
# Specify the name of the previous generation file to compare with the current generation file
|
||||
$P_FILE = "${WORK_DIR}\InstalledSoftware_Previous.csv"
|
||||
|
||||
# Specify the name of the current generation file to compare with the previous generation file
|
||||
$C_FILE = "${WORK_DIR}\InstalledSoftware_Current.csv"
|
||||
|
||||
# Function to append a line to a file
|
||||
Function Write-LineToFile {
|
||||
param([Parameter(Mandatory=$true)][string]$Path,[Parameter(Mandatory=$true)][string]$Line)
|
||||
If ( -not (Test-Path $Path) ) {
|
||||
#Write-Output "Creating file ""${Path}"""
|
||||
Try { New-Item -Path $Path -ItemType File -Force | Out-Null }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
Try { Add-Content -Path $Path -Value $Line }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
# Function to test whether or not an item has a specified property
|
||||
Function Test-ItemProperty {
|
||||
param([Parameter(Mandatory=$true)][string]$Path,[Parameter(Mandatory=$true)][string]$Name)
|
||||
Try {
|
||||
$test = Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue
|
||||
If (($null -eq $test) -or ($test.Length -eq 0)) { Return $false } Else { Return $true }
|
||||
}
|
||||
Catch { Return $false }
|
||||
}
|
||||
|
||||
# Function to retrieve an item property
|
||||
Function Get-ItemPropertyInfo {
|
||||
param([Parameter(Mandatory=$true)][string]$Path,[Parameter(Mandatory=$true)][string]$Name)
|
||||
If ( Test-ItemProperty -Path $Path -Name $Name ) {
|
||||
$retval = Get-ItemPropertyValue -Path $Path -Name $Name
|
||||
$retval = $retval -replace ",", " "
|
||||
|
||||
# Additional processing for specific properties
|
||||
Switch ( $Name ) {
|
||||
|
||||
# Convert the estimated size into MB
|
||||
"EstimatedSize" {
|
||||
$size = [int]$retval
|
||||
$size = [Math]::Ceiling($size / 1024)
|
||||
$retval = [string]$size
|
||||
}
|
||||
|
||||
# Convert the date format into YYYY-MM-DD
|
||||
"InstallDate" {
|
||||
|
||||
$origRetval = $retval
|
||||
|
||||
# Try to find each type of date string so we can convert it
|
||||
Switch -Regex ( $retval ) {
|
||||
'^\d{8}$' { $formatstring = 'yyyyMMdd' }
|
||||
'^\d{1,2}\\\d{1,2}\\\d{4}$' { $formatstring = 'MM\dd\yyyy' }
|
||||
'^\d{4}\\\d{1,2}\\\d{1,2}$' { $formatstring = 'yyyy\MM\dd' }
|
||||
Default { $formatstring = $null }
|
||||
}
|
||||
|
||||
# Make sure $formatstring has a value, if not just try to cast directly to a datetime
|
||||
Try {
|
||||
If ( $null -eq $formatstring ) {
|
||||
$date = [datetime]$retval
|
||||
} Else {
|
||||
$date = [datetime]::ParseExact($retval,$formatstring,$null)
|
||||
}
|
||||
|
||||
# Set $retval to a standard datetime format
|
||||
$retval = $date.ToString('yyyy-MM-dd')
|
||||
}
|
||||
Catch { Write-Error $_.Exception.Message; $retval = $origRetval }
|
||||
}
|
||||
Default {}
|
||||
}
|
||||
}
|
||||
Else { $retval = "" }
|
||||
|
||||
Return $retval
|
||||
}
|
||||
|
||||
# Create $WORK_DIR if it doesn't exist
|
||||
If ( -not (Test-Path $WORK_DIR) ) { New-Item -Path $WORK_DIR -ItemType Directory -Force | Out-Null }
|
||||
|
||||
# Create $C_FILE if it doesn't exist
|
||||
If ( -not (Test-Path $C_FILE) ) {
|
||||
# Write the headers to the output file
|
||||
Write-LineToFile -Path $C_FILE -Line "Name,Publisher,Version,InstallDate,EstimatedSize(MB),UninstallString,ModifyPath"
|
||||
|
||||
# Iterate through all Registry Uninstall keys and build the CSV file of all installed software
|
||||
ForEach ( $item in $(Get-ChildItem -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall','HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall') ) {
|
||||
$DisplayName = Get-ItemPropertyInfo -Path $item.PSPath -Name DisplayName
|
||||
$DisplayVersion = Get-ItemPropertyInfo -Path $item.PSPath -Name DisplayVersion
|
||||
$EstimatedSize = Get-ItemPropertyInfo -Path $item.PSPath -Name EstimatedSize
|
||||
$InstallDate = Get-ItemPropertyInfo -Path $item.PSPath -Name InstallDate
|
||||
$Publisher = Get-ItemPropertyInfo -Path $item.PSPath -Name Publisher
|
||||
$UninstallString = Get-ItemPropertyInfo -Path $item.PSPath -Name UninstallString
|
||||
$ModifyPath = Get-ItemPropertyInfo -Path $item.PSPath -Name ModifyPath
|
||||
|
||||
# Write installed software to output file only if it has a name and uninstall/modify path, otherwise discard
|
||||
If ( -not (($DisplayName -eq "") -and ($UninstallString -eq "") -and ($ModifyPath -eq "")) ) {
|
||||
$content = "${DisplayName},${Publisher},${DisplayVersion},${InstallDate},${EstimatedSize},${UninstallString},${ModifyPath}"
|
||||
Write-LineToFile -Path $C_FILE -Line $content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Compare $C_FILE and $P_FILE
|
||||
If ( (Test-Path $C_FILE) -and (Test-Path $P_FILE) ) {
|
||||
$current = Import-Csv -Path $C_FILE
|
||||
$previous = Import-Csv -Path $P_FILE
|
||||
|
||||
# Items that exist in $C_FILE but not $P_FILE have been installed
|
||||
$installed = Compare-Object -ReferenceObject $current -DifferenceObject $previous -Property Name,Version -PassThru | Where-Object { (![string]::IsNullOrEmpty($_.Name)) -and ($_.SideIndicator -eq '<=') }
|
||||
|
||||
# Items that exist in $P_FILE but not $C_FILE have been uninstalled
|
||||
$uninstalled = Compare-Object -ReferenceObject $current -DifferenceObject $previous -Property Name,Version -PassThru | Where-Object { (![string]::IsNullOrEmpty($_.Name)) -and ($_.SideIndicator -eq '=>') }
|
||||
|
||||
# Alert trigger for Atera
|
||||
If ( $installed -or $uninstalled ) { Write-Output "Application change detected" }
|
||||
|
||||
# Display installed apps
|
||||
If ( $installed ) {
|
||||
Write-Output "`nAPPLICATIONS RECENTLY INSTALLED:"
|
||||
$installed | Select-Object * -ExcludeProperty SideIndicator
|
||||
}
|
||||
|
||||
# Display uninstalled apps
|
||||
If ( $uninstalled ) {
|
||||
Write-Output "`nAPPLICATIONS RECENTLY UNINSTALLED:"
|
||||
$uninstalled | Select-Object * -ExcludeProperty UninstallString,ModifyPath,SideIndicator
|
||||
}
|
||||
}
|
||||
|
||||
# Delete $P_FILE
|
||||
If ( Test-Path $P_FILE ) {
|
||||
#Write-Output "Deleting ""${P_FILE}"""
|
||||
Remove-Item -Path $P_FILE -Force
|
||||
}
|
||||
|
||||
# Rename $C_FILE to $P_FILE
|
||||
If ( Test-Path $C_FILE ) {
|
||||
#Write-Output "Renaming ""${C_FILE}"" to ""${P_FILE}"""
|
||||
Move-Item -Path $C_FILE -Destination $P_FILE -Force
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@ If ( IsAdmin )
|
||||
{ $RegKey = 'HKLM:SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Adobe Flash Player NPAPI' }
|
||||
If ( $RegKey )
|
||||
{
|
||||
LogMsg "Found uninstall string: ${UninstallString}"
|
||||
Write-Output "Found uninstall string: ${UninstallString}"
|
||||
$UninstallString = (Get-ItemProperty -Path $RegKey).UninstallString
|
||||
$UninstallProgram = $UninstallString -replace ' -maintain plugin', ''
|
||||
LogMsg "Running: ""${UninstallProgram}"" -uninstall"
|
||||
Write-Output "Running: ""${UninstallProgram}"" -uninstall"
|
||||
Try { Start-Process "${UninstallProgram}" -ArgumentList "-uninstall" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
Else { LogMsg "Adobe Flash Player is not installed" }
|
||||
Else { Write-Output "Adobe Flash Player is not installed" }
|
||||
}
|
||||
Else { LogMsg "Must have administrative privileges to uninstall Adobe Flash" }
|
||||
Else { Write-Output "Must have administrative privileges to uninstall Adobe Flash" }
|
||||
+24
-24
@@ -8,7 +8,7 @@ Function Get-UninstallCodes ([string]$DisplayName) {
|
||||
If ( $(Get-ItemProperty -Path $_.PSPath -Name 'DisplayName' -ErrorAction SilentlyContinue) -and ($(Get-ItemPropertyValue -Path $_.PSPath -Name 'DisplayName' -ErrorAction SilentlyContinue) -eq $DisplayName) ) {
|
||||
$str = (Get-ItemPropertyValue -Path $_.PSPath -Name 'UninstallString')
|
||||
$code = $str.Substring(($str.Length - 37),36)
|
||||
LogMsg " - ${code}"
|
||||
Write-Output " - ${code}"
|
||||
$UninstallCodes.Add($code) | Out-Null
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ Function Get-ProductKeys ([string]$ProductName) {
|
||||
Get-ChildItem -Path 'HKCR:Installer\Products' | ForEach-Object {
|
||||
If ( $(Get-ItemProperty -Path $_.PSPath -Name 'ProductName' -ErrorAction SilentlyContinue) -and ($(Get-ItemPropertyValue -Path $_.PSPath -Name 'ProductName' -ErrorAction SilentlyContinue) -eq $ProductName) ) {
|
||||
$prod = $_.PSPath.Substring($_.PSPath.Length - 32)
|
||||
LogMsg " - ${prod}"
|
||||
Write-Output " - ${prod}"
|
||||
$ProductKeys.Add($prod) | Out-Null
|
||||
}
|
||||
}
|
||||
@@ -28,30 +28,30 @@ Function Get-ProductKeys ([string]$ProductName) {
|
||||
Function Get-ServiceStatus ([string]$Name) { (Get-Service -Name $Name -ErrorAction SilentlyContinue).Status }
|
||||
|
||||
Function Stop-RunningService ([string]$Name) {
|
||||
If ( $(Get-ServiceStatus -Name $Name) -eq "Running" ) { LogMsg "Stopping : ${Name} service" ; Stop-Service -Name $Name -Force }
|
||||
If ( $(Get-ServiceStatus -Name $Name) -eq "Running" ) { Write-Output "Stopping : ${Name} service" ; Stop-Service -Name $Name -Force }
|
||||
}
|
||||
|
||||
Function Remove-StoppedService ([string]$Name) {
|
||||
$s = (Get-ServiceStatus -Name $Name)
|
||||
If ( $s ) {
|
||||
If ( $s -eq "Stopped" ) {
|
||||
LogMsg "Deleting : ${Name} service"
|
||||
Write-Output "Deleting : ${Name} service"
|
||||
Start-Process "sc.exe" -ArgumentList "delete ${Name}" -Wait
|
||||
}
|
||||
} Else { LogMsg "Not Found: ${Name} service" }
|
||||
} Else { Write-Output "Not Found: ${Name} service" }
|
||||
}
|
||||
|
||||
Function Stop-RunningProcess ([string]$Name) {
|
||||
$p = (Get-Process -Name $_ -ErrorAction SilentlyContinue)
|
||||
If ( $p ) { LogMsg "Stopping : ${Name}.exe" ; $p | Stop-Process -Force }
|
||||
Else { LogMsg "Not Found: ${Name}.exe is not running"}
|
||||
If ( $p ) { Write-Output "Stopping : ${Name}.exe" ; $p | Stop-Process -Force }
|
||||
Else { Write-Output "Not Found: ${Name}.exe is not running"}
|
||||
}
|
||||
|
||||
Function Remove-Path ([string]$Path) {
|
||||
If ( Test-Path $Path ) {
|
||||
LogMsg "Deleting : ${Path}"
|
||||
Write-Output "Deleting : ${Path}"
|
||||
Remove-Item $Path -Recurse -Force
|
||||
} Else { LogMsg "Not Found: ${Path}" }
|
||||
} Else { Write-Output "Not Found: ${Path}" }
|
||||
}
|
||||
|
||||
Function Get-AllExeFiles ([string]$Path) {
|
||||
@@ -60,39 +60,39 @@ Function Get-AllExeFiles ([string]$Path) {
|
||||
}
|
||||
}
|
||||
|
||||
LogMsg "Uninstalling Atera agent"
|
||||
Write-Output "Uninstalling Atera agent"
|
||||
|
||||
LogMsg "Mounting HKEY_CLASSES_ROOT registry hive"
|
||||
Write-Output "Mounting HKEY_CLASSES_ROOT registry hive"
|
||||
New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT | Out-Null
|
||||
|
||||
LogMsg "Getting MSI package codes from the uninstall key"
|
||||
Write-Output "Getting MSI package codes from the uninstall key"
|
||||
$UninstallCodes = New-Object System.Collections.ArrayList
|
||||
'AteraAgent' | ForEach-Object { Get-UninstallCodes -DisplayName $_ }
|
||||
|
||||
LogMsg "Getting product keys from the list of installed products"
|
||||
Write-Output "Getting product keys from the list of installed products"
|
||||
$ProductKeys = New-Object System.Collections.ArrayList
|
||||
'AteraAgent' | ForEach-Object { Get-ProductKeys -ProductName $_ }
|
||||
|
||||
LogMsg "Directories that we may need to delete"
|
||||
Write-Output "Directories that we may need to delete"
|
||||
$Directories = @("${Env:ProgramFiles}\ATERA Networks")
|
||||
$Directories | ForEach-Object { LogMsg " - ${_}" }
|
||||
$Directories | ForEach-Object { Write-Output " - ${_}" }
|
||||
|
||||
LogMsg "Processes that we may need to terminate"
|
||||
Write-Output "Processes that we may need to terminate"
|
||||
$ExeFiles = New-Object System.Collections.ArrayList
|
||||
$Directories | ForEach-Object { Get-AllExeFiles -Path $_ }
|
||||
$ExeFiles.Add('reg') | Out-Null
|
||||
$ExeFiles | ForEach-Object { LogMsg " - ${_}.exe" }
|
||||
$ExeFiles | ForEach-Object { Write-Output " - ${_}.exe" }
|
||||
|
||||
LogMsg "Windows services that we may need to stop and/or delete"
|
||||
Write-Output "Windows services that we may need to stop and/or delete"
|
||||
$ServiceList = @('AteraAgent')
|
||||
$ServiceList | ForEach-Object { LogMsg " - ${_}" }
|
||||
$ServiceList | ForEach-Object { Write-Output " - ${_}" }
|
||||
|
||||
LogMsg "Windows Registry keys we may need to delete"
|
||||
Write-Output "Windows Registry keys we may need to delete"
|
||||
$RegistryKeys = @('HKLM:SOFTWARE\ATERA Networks')
|
||||
$RegistryKeys | ForEach-Object { LogMsg " - ${_}" }
|
||||
$RegistryKeys | ForEach-Object { Write-Output " - ${_}" }
|
||||
|
||||
# Uninstall each MSI package code in $UninstallCodes
|
||||
$UninstallCodes | ForEach-Object { LogMsg "Uninstall: ${_}" ; Start-Process "msiexec.exe" -ArgumentList "/X{${_}} /qn" -Wait }
|
||||
$UninstallCodes | ForEach-Object { Write-Output "Uninstall: ${_}" ; Start-Process "msiexec.exe" -ArgumentList "/X{${_}} /qn" -Wait }
|
||||
|
||||
# Stop services if they're still running
|
||||
$ServiceList | ForEach-Object { Stop-RunningService -Name $_ }
|
||||
@@ -112,10 +112,10 @@ $RegistryKeys | ForEach-Object { Remove-Path -Path $_ }
|
||||
# Delete remaining directories
|
||||
$Directories | ForEach-Object { Remove-Path -Path $_ }
|
||||
|
||||
LogMsg "Unmount HKEY_CLASSES_ROOT registry hive"
|
||||
Write-Output "Unmount HKEY_CLASSES_ROOT registry hive"
|
||||
Remove-PSDrive -Name HKCR
|
||||
|
||||
LogMsg "Finished uninstalling Atera Agent"
|
||||
Write-Output "Finished uninstalling Atera Agent"
|
||||
|
||||
# Delete this script
|
||||
Remove-Item $MyInvocation.MyCommand.Path -Force
|
||||
@@ -6,38 +6,38 @@ If ( $PATH )
|
||||
$SVC = Get-Service -Name "AdAppMgrSvc"
|
||||
If ( $SVC.Status -ne "Stopped" )
|
||||
{
|
||||
LogMsg "Stopping AdAppMgrSvc service"
|
||||
Write-Output "Stopping AdAppMgrSvc service"
|
||||
Try { $SVC | Stop-Service -Force }
|
||||
Catch { LogMsg $_.Exception.Message }
|
||||
Catch { Write-Output $_.Exception.Message }
|
||||
}
|
||||
|
||||
LogMsg "Stopping processes"
|
||||
Write-Output "Stopping processes"
|
||||
Try
|
||||
{
|
||||
Get-Process | Where { $_.Name -like "AdAppMgr" } | Stop-Process -Force
|
||||
Get-Process | Where { $_.Name -like "AutodeskDesktopApp" } | Stop-Process -Force
|
||||
Get-Process | Where-Object { $_.Name -like "AdAppMgr" } | Stop-Process -Force
|
||||
Get-Process | Where-Object { $_.Name -like "AutodeskDesktopApp" } | Stop-Process -Force
|
||||
}
|
||||
Catch { LogMsg $_.Exception.Message }
|
||||
Catch { Write-Output $_.Exception.Message }
|
||||
|
||||
LogMsg "Uninstalling Autodesk Desktop App"
|
||||
Write-Output "Uninstalling Autodesk Desktop App"
|
||||
Try { Start-Process -FilePath $PATH -ArgumentList '--mode unattended' -Wait }
|
||||
Catch { LogMsg $_.Exception.Message }
|
||||
Catch { Write-Output $_.Exception.Message }
|
||||
|
||||
If ( Test-Path "HKLM:\SOFTWARE\Wow6432Node\Autodesk\Autodesk Application Manager" ) {
|
||||
LogMsg "Removing: ""HKLM:\SOFTWARE\Wow6432Node\Autodesk\Autodesk Application Manager"""
|
||||
Write-Output "Removing: ""HKLM:\SOFTWARE\Wow6432Node\Autodesk\Autodesk Application Manager"""
|
||||
Remove-Item "HKLM:\SOFTWARE\Wow6432Node\Autodesk\Autodesk Application Manager" -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
If ( Test-Path "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Desktop App" ) {
|
||||
LogMsg "Removing: ""HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Desktop App"""
|
||||
Write-Output "Removing: ""HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Desktop App"""
|
||||
Remove-Item "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Desktop App" -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
If ( Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\AdAppMgrSvc.exe" ) {
|
||||
LogMsg "Removing: ""HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\AdAppMgrSvc.exe"""
|
||||
Write-Output "Removing: ""HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\AdAppMgrSvc.exe"""
|
||||
Remove-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\AdAppMgrSvc.exe" -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
If ( Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\AutodeskDesktopApp.exe" ) {
|
||||
LogMsg "Removing: ""HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\AutodeskDesktopApp.exe"""
|
||||
Write-Output "Removing: ""HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\AutodeskDesktopApp.exe"""
|
||||
Remove-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\AutodeskDesktopApp.exe" -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
Else { LogMsg "Autodesk Desktop App is not installed" }
|
||||
Else { Write-Output "Autodesk Desktop App is not installed" }
|
||||
@@ -1,8 +1,7 @@
|
||||
## Install and import additional Powershell modules
|
||||
Install-MSP360Module
|
||||
Import-Module -Name MSP360
|
||||
# Install and import additional Powershell modules
|
||||
Import-MSP360Module
|
||||
|
||||
## Uninstall the agent
|
||||
LogMsg "Uninstalling Emberkom Cloud Backup agent"
|
||||
# Uninstall the agent
|
||||
Write-Output "Uninstalling Emberkom Cloud Backup agent"
|
||||
Try { Remove-MBSAgent -RemoveSettings -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
## $APP_NAME must be set by the calling script or specified here
|
||||
|
||||
## Get all of the uninstall registry keys
|
||||
$UNINSTALL_KEYS = Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
|
||||
$UNINSTALL_KEYS += Get-ChildItem -Path HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall
|
||||
|
||||
Foreach ( $reg in $UNINSTALL_KEYS ) {
|
||||
## Get the uninstall key
|
||||
$REG_PROPERTY = Get-ItemProperty -Path $reg.PSPath
|
||||
|
||||
## Get the display name of the app
|
||||
$DISPLAY_NAME = $REG_PROPERTY.DisplayName
|
||||
|
||||
## Make sure the DisplayName matches what we're looking for
|
||||
If ( $DISPLAY_NAME -ilike $APP_NAME ) {
|
||||
## Make sure the software was installed using an MSI
|
||||
If ( $REG_PROPERTY.WindowsInstaller -eq 1 ) {
|
||||
## Get the install location
|
||||
If ( $REG_PROPERTY.InstallLocation ) { $INSTALL_LOCATION = $REG_PROPERTY.InstallLocation }
|
||||
|
||||
## Get the product code from the UninstallString
|
||||
If ( $REG_PROPERTY.UninstallString ) { $PRODUCT_CODE = $REG_PROPERTY.UninstallString.Replace('MsiExec.exe /X','') }
|
||||
|
||||
## Get the product code from the ModifyPath
|
||||
ElseIf ( $REG_PROPERTY.ModifyPath ) { $PRODUCT_CODE = $REG_PROPERTY.ModifyPath.Replace('MsiExec.exe /X','') }
|
||||
|
||||
## Uninstall the app
|
||||
If ( $PRODUCT_CODE ) {
|
||||
LogMsg "Uninstalling ${DISPLAY_NAME} ${PRODUCT_CODE}"
|
||||
Try { Start-Process -FilePath "msiexec.exe" -ArgumentList "/X${PRODUCT_CODE} /qn /norestart" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
|
||||
If ( $INSTALL_LOCATION ) {
|
||||
If ( Test-Path $INSTALL_LOCATION ) {
|
||||
LogMsg "Deleting install directory"
|
||||
Try { Remove-Item $INSTALL_LOCATION -Recurse -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,21 @@
|
||||
# Close all Office apps
|
||||
End-Process -Name 'POWERPNT' -Match
|
||||
End-Process -Name 'WINWORD' -Match
|
||||
End-Process -Name 'EXCEL' -Match
|
||||
End-Process -Name 'OUTLOOK' -Match
|
||||
End-Process -Name 'MSPUB' -Match
|
||||
End-Process -Name 'MSACCESS' -Match
|
||||
End-Process -Name 'Teams' -Match
|
||||
Start-Sleep -Seconds 5
|
||||
|
||||
End-Process -Name 'POWERPNT' -Match -Force
|
||||
End-Process -Name 'WINWORD' -Match -Force
|
||||
End-Process -Name 'EXCEL' -Match -Force
|
||||
End-Process -Name 'OUTLOOK' -Match -Force
|
||||
End-Process -Name 'MSPUB' -Match -Force
|
||||
End-Process -Name 'MSACCESS' -Match -Force
|
||||
End-Process -Name 'Teams' -Match -Force
|
||||
|
||||
# URL to the OffscrubC2R.vbs script
|
||||
$UninstallScriptURL = 'https://github.com/OfficeDev/Office-IT-Pro-Deployment-Scripts/raw/master/Office-ProPlus-Deployment/Deploy-OfficeClickToRun/OffScrubc2r.vbs'
|
||||
|
||||
@@ -8,6 +26,12 @@ $UninstallScript = Download-File -URL $UninstallScriptURL
|
||||
$ScriptArgs = "/ALL /QUIET /NOCANCEL"
|
||||
|
||||
# Run the script
|
||||
LogMsg "Uninstalling all Click2Run versions of Office"
|
||||
Write-Output "Uninstalling all Click2Run versions of Office"
|
||||
Try { Start-Process "cscript.exe" -ArgumentList "//nologo ""${UninstallScript}"" ${ScriptArgs}" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
|
||||
# Delete the downloaded script
|
||||
If ( Test-Path $UninstallScript ) {
|
||||
Write-Output "Deleting downloaded script: ""${UninstallScript}"""
|
||||
Remove-Item -Path $UninstallScript -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
@@ -1,38 +1,38 @@
|
||||
|
||||
## Remove N-Central Patch Management Service Controller
|
||||
If ( Test-Path "${Env:ProgramFiles(x86)}\MspPlatform\PME\unins000.exe" ) {
|
||||
LogMsg "Uninstalling N-Central Patch Management Service Controller"
|
||||
Write-Output "Uninstalling N-Central Patch Management Service Controller"
|
||||
Try { Start-Process "${Env:ProgramFiles(x86)}\MspPlatform\PME\unins000.exe" -ArgumentList "/SILENT" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
## Remove N-Central File Cache Service Agent
|
||||
If ( Test-Path "${Env:ProgramFiles(x86)}\MspPlatform\FileCacheServiceAgent\unins000.exe" ) {
|
||||
LogMsg "Uninstalling N-Central File Cache Service Agent"
|
||||
Write-Output "Uninstalling N-Central File Cache Service Agent"
|
||||
Try { Start-Process "${Env:ProgramFiles(x86)}\MspPlatform\FileCacheServiceAgent\unins000.exe" -ArgumentList "/SILENT" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
## Remove N-Central Request Handler Agent
|
||||
If ( Test-Path "${Env:ProgramFiles(x86)}\MspPlatform\RequestHandlerAgent\unins000.exe" ) {
|
||||
LogMsg "Uninstalling N-Central Request Handler Agent"
|
||||
Write-Output "Uninstalling N-Central Request Handler Agent"
|
||||
Try { Start-Process "${Env:ProgramFiles(x86)}\MspPlatform\RequestHandlerAgent\unins000.exe" -ArgumentList "/SILENT" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
## Remove Windows Software Probe
|
||||
If ( Test-Path "${ProgramFiles(x86)}\N-able Technologies\Windows Software Probe\" ) {
|
||||
$ProductCode = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Wow6432Node\N-able Technologies\Windows Software Probe").ProductCode
|
||||
LogMsg "Uninstalling Windows Software Probe"
|
||||
Write-Output "Uninstalling Windows Software Probe"
|
||||
Try { Start-Process "msiexec.exe" -ArgumentList "/X {${ProductCode}} /qn" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
## Remove Windows Agent
|
||||
If ( Test-Path "${ProgramFiles(x86)}\N-able Technologies\Windows Agent\" ) {
|
||||
LogMsg "Uninstalling N-Central Windows Agent"
|
||||
Write-Output "Uninstalling N-Central Windows Agent"
|
||||
Try { Start-Process "msiexec.exe" -ArgumentList "/X{F5873D07-85EE-4010-A461-B60989884B1C} /qn" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
## Remove NablePatchRepositoryService
|
||||
@@ -40,9 +40,9 @@ $ServiceName = "NablePatchRepositoryService"
|
||||
$Service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||
If ( $Service.Length -gt 0 ) {
|
||||
Control-Service -Stop -Name $ServiceName
|
||||
LogMsg "Deleting the service: ""${ServiceName}"""
|
||||
Write-Output "Deleting the service: ""${ServiceName}"""
|
||||
Try { Start-Process "sc" -ArgumentList "delete ${ServiceName}" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
## Remove Windows Software Probe Maintenance Service
|
||||
@@ -50,9 +50,9 @@ $ServiceName = "Windows Software Probe Maintenance Service"
|
||||
$Service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||
If ( $Service.Length -gt 0 ) {
|
||||
Control-Service -Stop -Name $ServiceName
|
||||
LogMsg "Deleting the service: ""${ServiceName}"""
|
||||
Write-Output "Deleting the service: ""${ServiceName}"""
|
||||
Try { Start-Process "sc" -ArgumentList "delete ${ServiceName}" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
## Remove Windows Software Probe Syslog Service
|
||||
@@ -60,9 +60,9 @@ $ServiceName = "Windows Software Probe Syslog Service"
|
||||
$Service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||
If ( $Service.Length -gt 0 ) {
|
||||
Control-Service -Stop -Name $ServiceName
|
||||
LogMsg "Deleting the service: ""${ServiceName}"""
|
||||
Write-Output "Deleting the service: ""${ServiceName}"""
|
||||
Try { Start-Process "sc" -ArgumentList "delete ${ServiceName}" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
## Remove Windows Software Probe Service
|
||||
@@ -70,9 +70,9 @@ $ServiceName = "Windows Software Probe Service"
|
||||
$Service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||
If ( $Service.Length -gt 0 ) {
|
||||
Control-Service -Stop -Name $ServiceName
|
||||
LogMsg "Deleting the service: ""${ServiceName}"""
|
||||
Write-Output "Deleting the service: ""${ServiceName}"""
|
||||
Try { Start-Process "sc" -ArgumentList "delete ${ServiceName}" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
## Remove Windows Agent Maintenance Service
|
||||
@@ -80,9 +80,9 @@ $ServiceName = "Windows Agent Maintenance Service"
|
||||
$Service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||
If ( $Service.Length -gt 0 ) {
|
||||
Control-Service -Stop -Name $ServiceName
|
||||
LogMsg "Deleting the service: ""${ServiceName}"""
|
||||
Write-Output "Deleting the service: ""${ServiceName}"""
|
||||
Try { Start-Process "sc" -ArgumentList "delete ${ServiceName}" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
## Remove Windows Agent Service
|
||||
@@ -90,33 +90,33 @@ $ServiceName = "Windows Agent Service"
|
||||
$Service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||
If ( $Service.Length -gt 0 ) {
|
||||
Control-Service -Stop -Name $ServiceName
|
||||
LogMsg "Deleting the service: ""${ServiceName}"""
|
||||
Write-Output "Deleting the service: ""${ServiceName}"""
|
||||
Try { Start-Process "sc" -ArgumentList "delete ${ServiceName}" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
## Remove "MspPlatform" directory
|
||||
If ( Test-Path "${Env:ProgramFiles(x86)}\MspPlatform" ) {
|
||||
LogMsg "Forcefully removing ""${Env:ProgramFiles(x86)}\MspPlatform"""
|
||||
Write-Output "Forcefully removing ""${Env:ProgramFiles(x86)}\MspPlatform"""
|
||||
Try { Remove-Item "${Env:ProgramFiles(x86)}\MspPlatform" -Recurse -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
## Remove "N-able Technologies" directory
|
||||
If ( Test-Path "${Env:ProgramFiles(x86)}\N-able Technologies" ) {
|
||||
LogMsg "Forcefully removing ""${Env:ProgramFiles(x86)}\N-able Technologies"""
|
||||
Write-Output "Forcefully removing ""${Env:ProgramFiles(x86)}\N-able Technologies"""
|
||||
Try { Remove-Item "${Env:ProgramFiles(x86)}\N-able Technologies" -Recurse -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
## Remove "N-able Technologies" registry key
|
||||
If ( Test-Path "HKLM:SOFTWARE\Wow6432Node\N-able Technologies" ) {
|
||||
LogMsg "Forcefully removing ""HKLM:SOFTWARE\Wow6432Node\N-able Technologies"""
|
||||
Write-Output "Forcefully removing ""HKLM:SOFTWARE\Wow6432Node\N-able Technologies"""
|
||||
Try { Remove-Item "HKLM:SOFTWARE\Wow6432Node\N-able Technologies" -Recurse -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
If ( Test-Path "HKLM:SOFTWARE\N-able Technologies" ) {
|
||||
LogMsg "Forcefully removing ""HKLM:SOFTWARE\N-able Technologies"""
|
||||
Write-Output "Forcefully removing ""HKLM:SOFTWARE\N-able Technologies"""
|
||||
Try { Remove-Item "HKLM:SOFTWARE\N-able Technologies" -Recurse -Force }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ Else
|
||||
}
|
||||
|
||||
# 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" )
|
||||
{ Start-Process "${INSTALLDIR}\uninstall.exe" -ArgumentList "/S" -Wait }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Make sure this script runs as admin
|
||||
If ( IsAdmin -eq $false ) { LogMsg "This script can only be run as admin" ; Exit }
|
||||
#If ( IsAdmin -eq $false ) { Write-Output "This script can only be run as admin" ; Exit }
|
||||
|
||||
# This is the list of Windows 10 AppX app names that we'll try to uninstall
|
||||
$AppList = @(
|
||||
@@ -57,31 +57,50 @@ If ( IsWindowsBuild -ge 9200 )
|
||||
$PackageFullName = $($(Get-AppxPackage $App).PackageFullName)
|
||||
$ProPackageFullName = $($(Get-AppxProvisionedPackage -Online | Where-Object {$_.Displayname -eq $App}).PackageName)
|
||||
|
||||
# If the package exists, uninstall it
|
||||
# Make sure we have a package
|
||||
If ($PackageFullName) {
|
||||
LogMsg “Removing package: ${PackageFullName}”
|
||||
|
||||
# Test if the package is an array
|
||||
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 { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
}
|
||||
|
||||
# If the provisioned package exists, uninstall it
|
||||
# Make sure we have a provisioned package
|
||||
If ($ProPackageFullName) {
|
||||
LogMsg “Removing provisioned package: ${ProPackageFullName}”
|
||||
|
||||
# Test if the provisioned package is an array
|
||||
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 { LogErr $_.Exception.Message }
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} Else { LogMsg "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 applications
|
||||
LogMsg "Uninstalling: Dell SupportAssist"
|
||||
Start-Process "msiexec.exe" -ArgumentList "/X{4F8A3BC3-641C-4B0D-AF46-EA3354016EA7} /qn /norestart" -Wait
|
||||
|
||||
LogMsg "Uninstalling: Dell SupportAssist Remediation"
|
||||
Start-Process "msiexec.exe" -ArgumentList "/X{2674B8DB-D696-4F9D-8606-E13E9D119579} /qn /norestart" -Wait
|
||||
|
||||
LogMsg "Uninstalling: Dell SupportAssist OS Recovery Plugin for Dell Update"
|
||||
Start-Process "msiexec.exe" -ArgumentList "/X{08E7C8D5-F2B5-4F09-B0EA-F28913BEFDB0} /qn /norestart" -Wait
|
||||
|
||||
LogMsg "Uninstalling: Dell Digital Delivery Services"
|
||||
Start-Process "msiexec.exe" -ArgumentList "/X{E530ABB7-9DCC-421B-B751-484375E8374A} /qn /norestart" -Wait
|
||||
# Uninstall unwanted Dell apps
|
||||
'Dell SupportAssist*', 'Dell Digital Delivery*', 'Dell Optimizer*', 'Dell Peripheral*', 'Dell Power Manager*', 'DellOptimizerUI*' | Uninstall-MSI
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"wireguard" | Uninstall-MSI -Match
|
||||
@@ -11,11 +11,11 @@ If (IsAdmin) {
|
||||
|
||||
## If the path to wireguard.exe is found, update the app
|
||||
If ( -not [string]::IsNullOrEmpty($PATH) ) {
|
||||
LogMsg "Updating Wireguard"
|
||||
Write-Output "Updating Wireguard"
|
||||
Try { Start-Process $PATH -ArgumentList "/update" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
LogMsg "Finished updating Wireguard"
|
||||
Catch { Write-Error $_.Exception.Message }
|
||||
Write-Output "Finished updating Wireguard"
|
||||
}
|
||||
}
|
||||
|
||||
Else { LogMsg "You must be a local administrator to update Wireguard" }
|
||||
Else { Write-Output "You must be a local administrator to update Wireguard" }
|
||||
@@ -0,0 +1,5 @@
|
||||
WEBROOT=/var/www/html
|
||||
if [ -d "${WEBROOT}/wp-content/plugins/wp-fail2ban/filters.d" ]; then
|
||||
echo "Updating fail2ban filters"
|
||||
cp -a "${WEBROOT}/wp-content/plugins/wp-fail2ban/filters.d/. /etc/fail2ban/filter.d/ && /usr/bin/fail2ban-client reload
|
||||
fi
|
||||
@@ -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 ) {
|
||||
LogMsg "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 { LogErr $_.Exception.Message }
|
||||
}
|
||||
Else {
|
||||
Try { Start-Process "${LFCLI}" -ArgumentList "filedrop /url:${FiledropURL} /from:""${From}"" /subject:""${Subject}"" /msg:""${Message}"" /c:n /f:""${FileToUpload}""" -Wait }
|
||||
Catch { LogErr $_.Exception.Message }
|
||||
}
|
||||
}
|
||||
Else { LogMsg "The specified file does not exist: ""${FileToUpload}""" }
|
||||
}
|
||||
Else { LogMsg "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.
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user