Restructured Documentation
Automatic Documentation Deployment / Sync Docs to https://kb.bunny-lab.io (push) Successful in 8s
Automatic Documentation Deployment / Sync Docs to https://kb.bunny-lab.io (push) Successful in 8s
This commit is contained in:
+11
-5
@@ -5,22 +5,28 @@ tags:
|
||||
- Scripting
|
||||
---
|
||||
|
||||
**Purpose**: Sometimes you will need to connect to Office365 via powershell in order to perform troubleshooting / automation that either is too complex to do via the website, or is not exposed / possible to do via the website.
|
||||
## Purpose
|
||||
Sometimes you will need to connect to Office365 via powershell in order to perform troubleshooting / automation that either is too complex to do via the website, or is not exposed / possible to do via the website.
|
||||
|
||||
## Update Nuget Package Manager
|
||||
``` powershell
|
||||
```powershell
|
||||
Install-PackageProvider -Name NuGet -Force -ForceBootstrap
|
||||
```
|
||||
|
||||
## Install ExchangeOnlineManagement Powershell Modules
|
||||
You will need to install and import the modules for Exchange Online before you can run the commands necessary for interacting with it.
|
||||
``` powershell
|
||||
|
||||
```powershell
|
||||
Install-Module -Name ExchangeOnlineManagement -Force
|
||||
Import-Module ExchangeOnlineManagement
|
||||
```
|
||||
|
||||
## Connect to Exchange Online
|
||||
When you run the following command, it will open a dialog box to take the username, password, and MFA code (if applicable) for an administrative account in the Exchange Online environment.
|
||||
``` powershell
|
||||
|
||||
```powershell
|
||||
Connect-ExchangeOnline -UserPrincipalName admin@domain.com
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Email Documentation](<../../../../reference/Applications/Email/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+9
-4
@@ -4,11 +4,12 @@ tags:
|
||||
- Email
|
||||
---
|
||||
|
||||
**Purpose**: Sometimes you need to set an autoreply on a mailbox on behalf of someone else. In these cases, you can leverage the "Exchange Admin Shell" to configure an auto-reply to anyone who sends an email to the mailbox.
|
||||
## Purpose
|
||||
Sometimes you need to set an autoreply on a mailbox on behalf of someone else. In these cases, you can leverage the "Exchange Admin Shell" to configure an auto-reply to anyone who sends an email to the mailbox.
|
||||
|
||||
In the example below, replace `<username>` with the shortened username of the target user. (e.g. `nicole.rappe` not `nicole.rappe@bunny-lab.io`)
|
||||
|
||||
``` powershell
|
||||
```powershell
|
||||
Set-MailboxAutoReplyConfiguration -Identity <username> -AutoReplyState Scheduled -StartTime "1/1/2025 00:00:00" -EndTime "1/15/2025 00:00:00" -InternalMessage "Example,<br><br>Message here.<br><br>Thank you." -ExternalMessage "Example,<br><br>Message here.<br><br>Thank you."
|
||||
```
|
||||
|
||||
@@ -17,10 +18,14 @@ Set-MailboxAutoReplyConfiguration -Identity <username> -AutoReplyState Scheduled
|
||||
|
||||
!!! example "Example Email Reply"
|
||||
The email auto reply will look something like this based on the command above.
|
||||
```
|
||||
|
||||
```text
|
||||
Example,
|
||||
|
||||
Message Here.
|
||||
|
||||
Thank you.
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Email Documentation](<../../../../reference/Applications/Email/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
tags:
|
||||
- Microsoft Exchange
|
||||
- Email
|
||||
---
|
||||
|
||||
## Purpose
|
||||
Start the Microsoft Exchange and supporting Windows services selected by this PowerShell script. The script sets their startup type to Automatic, so review the service requirements for the target server before running it.
|
||||
|
||||
!!! warning "Review Service Startup Changes"
|
||||
The preserved script sets every selected service to Automatic and starts it, including services that may intentionally be disabled. It does not perform a service restart. Do not use it as the return-to-service step for Exchange SE; follow the baseline-specific service validation in the rolling-update workflow.
|
||||
|
||||
```powershell
|
||||
$servicelist = Get-Service | Where-Object {$_.DisplayName -like "Microsoft Exchange *"}
|
||||
$servicelist += Get-Service | Where-Object {$_.DisplayName -eq "IIS Admin Service"}
|
||||
$servicelist += Get-Service | Where-Object { $_.DisplayName –eq "Windows Management Instrumentation" }
|
||||
$servicelist += Get-Service | Where-Object { $_.DisplayName –eq "World Wide Web Publishing Service" }
|
||||
|
||||
foreach($service in $servicelist){
|
||||
Set-Service $service -StartupType Automatic
|
||||
Start-Service $service
|
||||
}
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Exchange SE Maintenance](<../../../../workflows/Applications/Email/Microsoft Exchange/Perform Exchange SE DAG Rolling Updates.md>) — Use its service-baseline validation when maintaining an SE DAG.
|
||||
- [Related Email Documentation](<../../../../reference/Applications/Email/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
---
|
||||
tags:
|
||||
- DFS
|
||||
- Active Directory
|
||||
- PowerShell
|
||||
---
|
||||
|
||||
## Purpose
|
||||
Report the DFS namespaces, folder targets, and replication configuration in the current Active Directory domain. Run the script in PowerShell with the DFSN and DFSR modules and permission to query the domain.
|
||||
|
||||
## Script
|
||||
You may want to put together a simple table report of the DFS namespaces, replication info, and target folders. You can run the following powershell script to generate a nice table-based report of the current structure of the DFS namespaces in your domain.
|
||||
|
||||
??? example "Powershell Reporting Script"
|
||||
|
||||
```powershell
|
||||
# Automatically detect current AD domain and use it as DFS prefix
|
||||
try {
|
||||
$Domain = ([System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()).Name
|
||||
$DomainPrefix = "\\$Domain"
|
||||
} catch {
|
||||
Write-Warning "Unable to detect domain automatically. Falling back to manual value."
|
||||
$DomainPrefix = "\\bunny-lab.io"
|
||||
}
|
||||
|
||||
Import-Module DFSN -ErrorAction Stop
|
||||
Import-Module DFSR -ErrorAction Stop
|
||||
|
||||
function Get-ServerNameFromPath {
|
||||
param([string]$Path)
|
||||
if ([string]::IsNullOrWhiteSpace($Path)) { return $null }
|
||||
if ($Path -like "\\*") { return ($Path -split '\\')[2] }
|
||||
return $null
|
||||
}
|
||||
function Get-Max3 {
|
||||
param([int[]]$Values)
|
||||
if (-not $Values) { return 0 }
|
||||
return (($Values | Measure-Object -Maximum).Maximum)
|
||||
}
|
||||
|
||||
# Build: GroupName (lower) -> memberships[]
|
||||
$allGroups = Get-DfsReplicationGroup -ErrorAction SilentlyContinue
|
||||
$groupMembershipMap = @{}
|
||||
foreach ($g in $allGroups) {
|
||||
$ms = Get-DfsrMembership -GroupName $g.GroupName -ErrorAction SilentlyContinue
|
||||
$groupMembershipMap[$g.GroupName.ToLower()] = $ms
|
||||
}
|
||||
|
||||
# Flatten all memberships for regex fallback
|
||||
$allMemberships = @()
|
||||
foreach ($arr in $groupMembershipMap.Values) { if ($arr) { $allMemberships += $arr } }
|
||||
|
||||
$rows = New-Object System.Collections.Generic.List[psobject]
|
||||
|
||||
# Enumerate namespace roots
|
||||
$roots = Get-DfsnRoot -ErrorAction Stop | Where-Object { $_.Path -like "$DomainPrefix\*" }
|
||||
|
||||
Write-Host "DFS Namespace and Replication Overview" -ForegroundColor Cyan
|
||||
Write-Host "------------------------------------------------------`n"
|
||||
|
||||
foreach ($root in $roots) {
|
||||
|
||||
$rootPath = $root.Path
|
||||
$rootLeaf = ($rootPath -split '\\')[-1]
|
||||
|
||||
$nsServers = @()
|
||||
$rootTargets = Get-DfsnRootTarget -Path $rootPath -ErrorAction SilentlyContinue
|
||||
foreach ($rt in $rootTargets) {
|
||||
$srv = Get-ServerNameFromPath $rt.TargetPath
|
||||
if ($srv) { $nsServers += $srv }
|
||||
}
|
||||
|
||||
# Folders under this root
|
||||
$folders = Get-DfsnFolder -Path "$rootPath\*" -ErrorAction SilentlyContinue | Sort-Object Path
|
||||
|
||||
foreach ($f in $folders) {
|
||||
$namespaceFull = $f.Path
|
||||
$leaf = ($f.Path -split '\\')[-1]
|
||||
|
||||
# DFSN folder targets
|
||||
$targets = Get-DfsnFolderTarget -Path $f.Path -ErrorAction SilentlyContinue
|
||||
$targets = @($targets | Sort-Object { Get-ServerNameFromPath $_.TargetPath }) # ensure array
|
||||
|
||||
# Map to DFSR group by naming; fallback to regex on ContentPath
|
||||
$candidateGroup = ((($rootPath -replace '^\\\\','') + '\' + $leaf).ToLower())
|
||||
if ($groupMembershipMap.ContainsKey($candidateGroup)) {
|
||||
$msForFolder = $groupMembershipMap[$candidateGroup]
|
||||
} else {
|
||||
$escapedRootLeaf = [regex]::Escape($rootLeaf)
|
||||
$escapedLeaf = [regex]::Escape($leaf)
|
||||
$regex = "\\$escapedRootLeaf\\$escapedLeaf($|\\)"
|
||||
$msForFolder = $allMemberships | Where-Object { $_.ContentPath -imatch $regex }
|
||||
}
|
||||
$msForFolder = @($msForFolder) # normalize to array
|
||||
|
||||
# Build aligned rows: one per target
|
||||
$targetLines = @()
|
||||
$replLines = @()
|
||||
|
||||
foreach ($t in $targets) {
|
||||
$tServer = Get-ServerNameFromPath $t.TargetPath
|
||||
$targetLines += $t.TargetPath
|
||||
|
||||
$msForServer = $null
|
||||
if ($msForFolder.Count -gt 0) {
|
||||
$msForServer = $msForFolder | Where-Object { $_.ComputerName -ieq $tServer } | Select-Object -First 1
|
||||
}
|
||||
if ($msForServer -and $msForServer.ContentPath) { $replLines += $msForServer.ContentPath } else { $replLines += '' }
|
||||
}
|
||||
|
||||
# Max line count for row expansion (PS 5.1 safe)
|
||||
$maxLines = Get-Max3 @($targetLines.Count, $replLines.Count, $nsServers.Count)
|
||||
|
||||
for ($i = 0; $i -lt $maxLines; $i++) {
|
||||
|
||||
# Precompute values (PS 5.1: no inline-if in hashtables)
|
||||
$nsVal = ''
|
||||
if ($i -eq 0) { $nsVal = $namespaceFull }
|
||||
|
||||
$targetVal = ''
|
||||
if ($i -lt $targetLines.Count) { $targetVal = $targetLines[$i] }
|
||||
|
||||
$replVal = ''
|
||||
if ($i -lt $replLines.Count) { $replVal = $replLines[$i] }
|
||||
|
||||
$nsServerVal = ''
|
||||
if ($i -lt $nsServers.Count) { $nsServerVal = $nsServers[$i] }
|
||||
|
||||
$row = [PSCustomObject]@{
|
||||
'Namespace' = $nsVal
|
||||
'Member Folder Target(s)' = $targetVal
|
||||
'Replication Locations' = $replVal
|
||||
'Namespace Servers' = $nsServerVal
|
||||
}
|
||||
$rows.Add($row) | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Render as a PowerShell bordered grid with one-space left/right padding in every cell
|
||||
function Write-DfsGrid {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[System.Collections.IEnumerable]$Data,
|
||||
|
||||
[string[]]$Columns = @('Namespace','Member Folder Target(s)','Replication Locations','Namespace Servers'),
|
||||
|
||||
# Reasonable max widths; tune to your console (these are content+padding widths)
|
||||
[int[]]$MaxWidths = @(70, 70, 52, 30),
|
||||
|
||||
[switch]$Ascii # use +-| instead of box-drawing if your console garbles Unicode
|
||||
)
|
||||
|
||||
# Ensure arrays align
|
||||
if ($MaxWidths.Count -lt $Columns.Count) {
|
||||
$pad = New-Object System.Collections.Generic.List[int]
|
||||
$pad.AddRange($MaxWidths)
|
||||
for ($i=$MaxWidths.Count; $i -lt $Columns.Count; $i++) { $pad.Add(40) }
|
||||
$MaxWidths = $pad.ToArray()
|
||||
}
|
||||
|
||||
# Characters
|
||||
if ($Ascii) {
|
||||
$H = @{ tl='+'; tr='+'; bl='+'; br='+'; hz='-'; vt='|'; tj='+'; mj='+'; bj='+' }
|
||||
} else {
|
||||
# Box-drawing
|
||||
$H = @{ tl='┌'; tr='┐'; bl='└'; br='┘'; hz='─'; vt='│'; tj='┬'; mj='┼'; bj='┴' }
|
||||
try { [Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8 } catch {}
|
||||
}
|
||||
|
||||
function TruncPad([string]$s, [int]$w) {
|
||||
if ($null -eq $s) { $s = '' }
|
||||
$s = $s -replace '\r','' -replace '\t',' '
|
||||
if ($s.Length -le $w) { return $s.PadRight($w, ' ') }
|
||||
if ($w -le 1) { return $s.Substring(0, $w) }
|
||||
return ($s.Substring(0, $w-1) + '…')
|
||||
}
|
||||
|
||||
# Materialize and compute widths (include one-space left/right padding for header and data)
|
||||
$rows = @($Data | ForEach-Object {
|
||||
$o = @{}
|
||||
foreach ($c in $Columns) { $o[$c] = [string]($_.$c) }
|
||||
[pscustomobject]$o
|
||||
})
|
||||
|
||||
$widths = @()
|
||||
for ($i=0; $i -lt $Columns.Count; $i++) {
|
||||
$col = $Columns[$i]
|
||||
# Start with header length including padding
|
||||
$max = (" " + $col + " ").Length
|
||||
foreach ($r in $rows) {
|
||||
$len = (" " + [string]$r.$col + " ").Length
|
||||
if ($len -gt $max) { $max = $len }
|
||||
}
|
||||
$widths += [Math]::Min($max, $MaxWidths[$i])
|
||||
}
|
||||
|
||||
# Line builders
|
||||
function DrawTop() {
|
||||
$line = $H.tl
|
||||
for ($i = 0; $i -lt $widths.Count; $i++) {
|
||||
$line += ($H.hz * $widths[$i])
|
||||
if ($i -lt ($widths.Count - 1)) {
|
||||
$line += $H.tj
|
||||
} else {
|
||||
$line += $H.tr
|
||||
}
|
||||
}
|
||||
$line
|
||||
}
|
||||
function DrawMid([string[]]$Columns, [int[]]$widths, $H) {
|
||||
$line = $H.vt
|
||||
for ($i=0; $i -lt $widths.Count; $i++) {
|
||||
$line += TruncPad (" " + $Columns[$i] + " ") $widths[$i]
|
||||
$line += $H.vt
|
||||
}
|
||||
$line
|
||||
}
|
||||
function DrawSep() {
|
||||
$line = $H.vt
|
||||
for ($i=0; $i -lt $widths.Count; $i++) {
|
||||
$line += ($H.hz * $widths[$i])
|
||||
$line += $H.vt
|
||||
}
|
||||
$line
|
||||
}
|
||||
function DrawHeaderSep() {
|
||||
$line = $H.vt
|
||||
for ($i=0; $i -lt $widths.Count; $i++) {
|
||||
$line += ($H.hz * $widths[$i])
|
||||
$line += $H.vt
|
||||
}
|
||||
$line
|
||||
}
|
||||
function DrawBottom() {
|
||||
$line = $H.bl
|
||||
for ($i = 0; $i -lt $widths.Count; $i++) {
|
||||
$line += ($H.hz * $widths[$i])
|
||||
if ($i -lt ($widths.Count - 1)) {
|
||||
$line += $H.bj
|
||||
} else {
|
||||
$line += $H.br
|
||||
}
|
||||
}
|
||||
$line
|
||||
}
|
||||
function DrawRow($r, [string[]]$Columns, [int[]]$widths, $H) {
|
||||
$line = $H.vt
|
||||
for ($i=0; $i -lt $widths.Count; $i++) {
|
||||
$val = [string]$r.($Columns[$i])
|
||||
$line += TruncPad (" " + $val + " ") $widths[$i]
|
||||
$line += $H.vt
|
||||
}
|
||||
$line
|
||||
}
|
||||
|
||||
# Render with group separators between namespaces (when the Namespace cell is non-empty)
|
||||
Write-Host (DrawTop)
|
||||
Write-Host (DrawMid -Columns $Columns -widths $widths -H $H)
|
||||
Write-Host (DrawHeaderSep)
|
||||
|
||||
$first = $true
|
||||
foreach ($r in $rows) {
|
||||
if (-not $first -and ([string]$r.$($Columns[0])) ) {
|
||||
# Namespace changed → draw a separator
|
||||
Write-Host (DrawSep)
|
||||
}
|
||||
$first = $false
|
||||
Write-Host (DrawRow -r $r -Columns $Columns -widths $widths -H $H)
|
||||
}
|
||||
|
||||
Write-Host (DrawBottom)
|
||||
}
|
||||
|
||||
Write-DfsGrid -Data $rows
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [DFS Deployment](<../../../../deployments/Applications/Files and Collaboration/Windows Server/DFS Namespaces with Replication.md>) — Review the namespace and replication structure that this report inspects.
|
||||
- [Related Files and Collaboration Documentation](<../../../../reference/Applications/Files and Collaboration/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
@@ -0,0 +1,132 @@
|
||||
---
|
||||
tags:
|
||||
- DFS
|
||||
- Replication
|
||||
- PowerShell
|
||||
---
|
||||
|
||||
## Purpose
|
||||
Report the directional replication backlog between the DFS member servers in the script. Set the member names to the intended deployment and run from a PowerShell session with access to its DFS replication configuration.
|
||||
|
||||
## Script
|
||||
You may want to check that replication is occurring bi-directionally between every member server in your DFS deployment. I wrote a script below that effectively shows you every replication group and each directional backlog status.
|
||||
|
||||
```powershell
|
||||
# --- CONFIG ---
|
||||
$Members = @("LAB-FPS-01","LAB-FPS-02")
|
||||
$SummarizeAcrossFolders = $true # $true = one line per direction per RG; $false = per-folder lines
|
||||
|
||||
function Invoke-DfsrBacklogStatus {
|
||||
param(
|
||||
[Parameter(Mandatory)] [string] $RG,
|
||||
[Parameter(Mandatory)] [string] $RF,
|
||||
[Parameter(Mandatory)] [string] $Send,
|
||||
[Parameter(Mandatory)] [string] $Recv
|
||||
)
|
||||
|
||||
$out = & dfsrdiag backlog /rgname:"$RG" /rfname:"$RF" /sendingmember:"$Send" /receivingmember:"$Recv" 2>&1 | Out-String
|
||||
$outTrim = ($out -split "`r?`n" | ForEach-Object { $_.Trim() }) | Where-Object { $_ -ne "" }
|
||||
|
||||
if ($out -match 'No Backlog') {
|
||||
return [pscustomobject]@{ Status="No Backlog"; Count=0; Detail=$null }
|
||||
}
|
||||
|
||||
$count = $null
|
||||
$countLine = $outTrim | Where-Object { $_ -match '(?i)backlog' } | Select-Object -First 1
|
||||
if ($countLine -and ($countLine -match '(\d+)')) { $count = [int]$matches[1] }
|
||||
|
||||
$detail = ($outTrim | Select-Object -First 8) -join " | "
|
||||
|
||||
return [pscustomobject]@{
|
||||
Status = if ($count -ne $null) { "Backlog: $count" } else { "Backlog/Check Output" }
|
||||
Count = $count
|
||||
Detail = $detail
|
||||
}
|
||||
}
|
||||
|
||||
$groups = Get-DfsReplicationGroup | Sort-Object GroupName
|
||||
|
||||
foreach ($g in $groups) {
|
||||
$rg = $g.GroupName
|
||||
$rfs = Get-DfsReplicatedFolder -GroupName $rg | Sort-Object FolderName
|
||||
|
||||
Write-Host ""
|
||||
Write-Host ("== Replication Group: {0} ==" -f $rg)
|
||||
|
||||
foreach ($send in $Members) {
|
||||
foreach ($recv in $Members) {
|
||||
if ($send -eq $recv) { continue }
|
||||
|
||||
if ($SummarizeAcrossFolders) {
|
||||
$worstCount = 0
|
||||
$nonZero = @()
|
||||
$errorsOrDetails = @()
|
||||
|
||||
foreach ($rfObj in $rfs) {
|
||||
$rf = $rfObj.FolderName
|
||||
$res = Invoke-DfsrBacklogStatus -RG $rg -RF $rf -Send $send -Recv $recv
|
||||
|
||||
if ($res.Status -ne "No Backlog") {
|
||||
$nonZero += [pscustomobject]@{ RF=$rf; Status=$res.Status; Count=$res.Count; Detail=$res.Detail }
|
||||
if ($res.Count -ne $null -and $res.Count -gt $worstCount) { $worstCount = $res.Count }
|
||||
|
||||
# ✅ FIX: ${rf} avoids the ':' parsing issue
|
||||
if ($res.Detail) { $errorsOrDetails += "RF=${rf}: $($res.Detail)" }
|
||||
}
|
||||
}
|
||||
|
||||
if ($nonZero.Count -eq 0) {
|
||||
Write-Host ("{0} -> {1}: No Backlog" -f $send, $recv)
|
||||
} else {
|
||||
if ($worstCount -gt 0) {
|
||||
Write-Host ("{0} -> {1}: Backlog (max {2} across RFs)" -f $send, $recv, $worstCount)
|
||||
} else {
|
||||
Write-Host ("{0} -> {1}: Backlog/Errors (see details)" -f $send, $recv)
|
||||
}
|
||||
|
||||
$errorsOrDetails | Select-Object -First 5 | ForEach-Object { Write-Host (" - {0}" -f $_) }
|
||||
if ($errorsOrDetails.Count -gt 5) { Write-Host " - ... (more omitted)" }
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach ($rfObj in $rfs) {
|
||||
$rf = $rfObj.FolderName
|
||||
$res = Invoke-DfsrBacklogStatus -RG $rg -RF $rf -Send $send -Recv $recv
|
||||
|
||||
if ($res.Status -eq "No Backlog") {
|
||||
Write-Host ("{0} -> {1} [{2}]: No Backlog" -f $send, $recv, $rf)
|
||||
} else {
|
||||
Write-Host ("{0} -> {1} [{2}]: {3}" -f $send, $recv, $rf, $res.Status)
|
||||
if ($res.Detail) { Write-Host (" - {0}" -f $res.Detail) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
!!! example "Example Output"
|
||||
You will see output like the following when you run the script.
|
||||
|
||||
```powershell
|
||||
== Replication Group: bunny-lab.io\music\fl studio plugins ==
|
||||
LAB-FPS-01 -> LAB-FPS-02: No Backlog
|
||||
LAB-FPS-02 -> LAB-FPS-01: No Backlog
|
||||
|
||||
== Replication Group: bunny-lab.io\music\personal music ==
|
||||
LAB-FPS-01 -> LAB-FPS-02: No Backlog
|
||||
LAB-FPS-02 -> LAB-FPS-01: No Backlog
|
||||
|
||||
== Replication Group: bunny-lab.io\music\shared music ==
|
||||
LAB-FPS-01 -> LAB-FPS-02: No Backlog
|
||||
LAB-FPS-02 -> LAB-FPS-01: No Backlog
|
||||
|
||||
== Replication Group: bunny-lab.io\projects\coding ==
|
||||
LAB-FPS-01 -> LAB-FPS-02: No Backlog
|
||||
LAB-FPS-02 -> LAB-FPS-01: No Backlog
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [DFS Deployment](<../../../../deployments/Applications/Files and Collaboration/Windows Server/DFS Namespaces with Replication.md>) — Identify the replication members and folders.
|
||||
- [Related Files and Collaboration Documentation](<../../../../reference/Applications/Files and Collaboration/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+7
-4
@@ -4,7 +4,7 @@ tags:
|
||||
- Scripting
|
||||
---
|
||||
|
||||
**Purpose**:
|
||||
## Purpose
|
||||
Sometimes you just need a basic script that outputs a pretty directory and file tree. This script offers files and folders to ignore, and outputs a fancy directory tree.
|
||||
|
||||
```powershell
|
||||
@@ -26,8 +26,8 @@ function Export-Tree {
|
||||
[string]$Prefix
|
||||
)
|
||||
|
||||
$items = Get-ChildItem -Path $Folder -Force | Where-Object {
|
||||
$_.Name -ne "." -and $_.Name -ne ".." -and
|
||||
$items = Get-ChildItem -Path $Folder -Force | Where-Object {
|
||||
$_.Name -ne "." -and $_.Name -ne ".." -and
|
||||
($global:IgnoreList -notcontains $_.Name)
|
||||
} | Sort-Object PSIsContainer, Name
|
||||
|
||||
@@ -51,4 +51,7 @@ function Export-Tree {
|
||||
|
||||
# Run it
|
||||
Export-Tree -Path "." -OutFile "directory_tree.txt"
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Files and Collaboration Documentation](<../../../reference/Applications/Files and Collaboration/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+6
-3
@@ -5,10 +5,10 @@ tags:
|
||||
- Scripting
|
||||
---
|
||||
|
||||
**Purpose**:
|
||||
## Purpose
|
||||
Locate specific files, and copy them with a renamed datestamp appended to a specific directory.
|
||||
|
||||
``` powershell
|
||||
```powershell
|
||||
# Define an array of objects, each having a prefix and a suffix
|
||||
$files = @(
|
||||
@{Prefix="name"; Suffix="Extension"},
|
||||
@@ -41,4 +41,7 @@ foreach ($file in $files) {
|
||||
Copy-Item -Path $match.FullName -Destination (Join-Path -Path $destination -ChildPath $newName)
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Files and Collaboration Documentation](<../../../reference/Applications/Files and Collaboration/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+8
-5
@@ -6,10 +6,10 @@ tags:
|
||||
- Scripting
|
||||
---
|
||||
|
||||
**Purpose**:
|
||||
## Purpose
|
||||
This script will iterate over all network shares hosted by the computer it is running on, and will give *recursive* permissions to all folders, subfolders, and files, including hidden ones. It is very I/O intensive given it iterates recursively on every file/folder being shared.
|
||||
|
||||
``` powershell
|
||||
```powershell
|
||||
$AllShares = Get-SMBShare | Where-Object {$_.Description -NotMatch "Default share|Remote Admin|Remote IPC|Printer Drivers"} | Select-Object -ExpandProperty Path
|
||||
$Output = @()
|
||||
ForEach ($SMBDirectory in $AllShares)
|
||||
@@ -17,13 +17,16 @@ ForEach ($SMBDirectory in $AllShares)
|
||||
$FolderPath = Get-ChildItem -Directory -Path $SMBDirectory -Recurse -Force
|
||||
ForEach ($Folder in $FolderPath) {
|
||||
$Acl = Get-Acl -Path $Folder.FullName
|
||||
ForEach ($Access in $Acl.Access)
|
||||
ForEach ($Access in $Acl.Access)
|
||||
{
|
||||
$Properties = [ordered]@{'Folder Name'=$Folder.FullName;'Group/User'=$Access.IdentityReference;'Permissions'=$Access.FileSystemRights;'Inherited'=$Access.IsInherited}
|
||||
$Output += New-Object -TypeName PSObject -Property $Properties
|
||||
$Output += New-Object -TypeName PSObject -Property $Properties
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$Output | Export-CSV -Path C:\SMB_REPORT.csv -NoTypeInformation -Append
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Files and Collaboration Documentation](<../../../../reference/Applications/Files and Collaboration/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+6
-3
@@ -6,13 +6,16 @@ tags:
|
||||
- Scripting
|
||||
---
|
||||
|
||||
**Purpose**:
|
||||
## Purpose
|
||||
This script will iterate over all network shares hosted by the computer it is running on, and will give *top-level* permissions to all the shared folders. It will not navigate deeper than the top-level in its report. Very I/O friendly.
|
||||
|
||||
``` powershell
|
||||
```powershell
|
||||
$AllShares = Get-SMBShare | Where-Object {$_.Description -NotMatch "Default share|Remote Admin|Remote IPC|Printer Drivers"} | Select-Object -ExpandProperty Name
|
||||
ForEach ($SMBDirectory in $AllShares)
|
||||
{
|
||||
Get-SMBShareAccess -Name $SMBDirectory | Export-CSV -Path C:\SMB_REPORT.csv -NoTypeInformation -Append
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Files and Collaboration Documentation](<../../../../reference/Applications/Files and Collaboration/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+15
-11
@@ -5,12 +5,13 @@ tags:
|
||||
- Scripting
|
||||
---
|
||||
|
||||
**Purpose**: In some unique cases, you want to be able to either perform backups of data or exfiltrate data to Nextcloud from a local device via the use of a script. Doing such a thing with Nextcloud as the destination is not very documented, but you can achieve that result by running a script like what is seen below:
|
||||
## Purpose
|
||||
In some unique cases, you want to be able to either perform backups of data or exfiltrate data to Nextcloud from a local device via the use of a script. Doing such a thing with Nextcloud as the destination is not very documented, but you can achieve that result by running a script like what is seen below:
|
||||
|
||||
## Windows
|
||||
!!! abstract "Environment Variables"
|
||||
You will need to assign the following variables either within the script or externally via environment variables at the time the script is executed.
|
||||
|
||||
You will need to assign the following variables either within the script or externally via environment variables at the time the script is executed.
|
||||
|
||||
| **Variable** | **Default Value** | **Description** |
|
||||
| :--- | :--- | :--- |
|
||||
| `NEXTCLOUD_SERVER_URL` | `https://cloud.bunny-lab.io` | This is the base URL of the Nextcloud server that data will be copied to. |
|
||||
@@ -22,7 +23,7 @@ tags:
|
||||
| `LOGFILE` | `C:\Windows\Temp\nc_pull.log` | This file is how the script has "persistence". In case the computer is shut down, rebooted, etc, when it comes back online and the script is re-ran against it, it reads this file to pick up where it last was, and attempts to resume from that point. If this transfer is meant to be hidden, put this file somewhere someone is not likely to find it easily. |
|
||||
|
||||
### Powershell Script
|
||||
``` powershell
|
||||
```powershell
|
||||
# --------------------------
|
||||
# Function for File Upload Logic
|
||||
# --------------------------
|
||||
@@ -59,7 +60,7 @@ Function Upload-Files ($targetDir) {
|
||||
|
||||
# Record this file in the log since it was successfully uploaded
|
||||
Add-Content -Path $LOGFILE -Value $_.FullName
|
||||
|
||||
|
||||
} else {
|
||||
Write-Host "Skipping previously uploaded file $($_.FullName)"
|
||||
}
|
||||
@@ -99,8 +100,8 @@ Function Upload-Files ($targetDir) {
|
||||
|
||||
## MacOS/Linux
|
||||
!!! abstract "Environment Variables"
|
||||
You will need to assign the following variables either within the script or externally via environment variables at the time the script is executed.
|
||||
|
||||
You will need to assign the following variables either within the script or externally via environment variables at the time the script is executed.
|
||||
|
||||
| **Variable** | **Default Value** | **Description** |
|
||||
| :--- | :--- | :--- |
|
||||
| `NEXTCLOUD_SERVER_URL` | `https://cloud.bunny-lab.io` | This is the base URL of the Nextcloud server that data will be copied to. |
|
||||
@@ -109,8 +110,8 @@ Function Upload-Files ($targetDir) {
|
||||
| `DATA_TO_COPY` | `/home/bunny/example` | This directory target is the primary focus of the upload / backup / exfiltration. The script will iterate through this target first before it moves onto the secondary target. The target can be a directory or a single file. This will act as the main priority of the transfer. |
|
||||
| `LOGFILE` | `/tmp/uploaded_files.log` | This file is how the script has "persistence". In case the computer is shut down, rebooted, etc, when it comes back online and the script is re-ran against it, it reads this file to pick up where it last was, and attempts to resume from that point. If this transfer is meant to be hidden, put this file somewhere someone is not likely to find it easily. |
|
||||
|
||||
### Bash Script
|
||||
``` sh
|
||||
### Bash Script
|
||||
```sh
|
||||
#!/bin/bash
|
||||
|
||||
# Directory to search
|
||||
@@ -128,7 +129,7 @@ fi
|
||||
find "$DIR" -type f -print0 | while IFS= read -r -d '' file; do
|
||||
# Extract just the filename
|
||||
filename=$(basename "$file")
|
||||
|
||||
|
||||
# Check if this file has been uploaded before
|
||||
if ! grep -q "$file" "$LOGFILE"; then
|
||||
echo "Uploading $file ..."
|
||||
@@ -153,4 +154,7 @@ find "$DIR" -type f -print0 | while IFS= read -r -d '' file; do
|
||||
echo "Skipping previously uploaded file $file"
|
||||
fi
|
||||
done
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Files and Collaboration Documentation](<../../../reference/Applications/Files and Collaboration/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+8
-4
@@ -5,7 +5,8 @@ tags:
|
||||
- Scripting
|
||||
---
|
||||
|
||||
**Purpose**: This script was purpose-built for the homelab Minecraft servers in my homelab. It may need to be ported based on your own needs.
|
||||
## Purpose
|
||||
This script was purpose-built for the homelab Minecraft servers in my homelab. It may need to be ported based on your own needs.
|
||||
|
||||
```powershell
|
||||
clear
|
||||
@@ -34,7 +35,7 @@ function Get-ZipFileName {
|
||||
Write-Host "File not found! Please check the file name and try again." -ForegroundColor Red
|
||||
exit
|
||||
}
|
||||
|
||||
|
||||
Write-Host "ZIP file found: $zipFilePath" -ForegroundColor Green
|
||||
return $zipFilePath
|
||||
}
|
||||
@@ -103,7 +104,7 @@ function Rename-OldServer {
|
||||
|
||||
$currentDate = Get-Date -Format "MM-dd-yyyy"
|
||||
$backupFolderPath = "$oldFolderPath.backup.$currentDate"
|
||||
|
||||
|
||||
Write-Host "Step 4: Renaming old server folder to: $backupFolderPath" -ForegroundColor Yellow
|
||||
Rename-Item -Path $oldFolderPath -NewName $backupFolderPath
|
||||
Write-Host "Old server folder renamed to: $backupFolderPath" -ForegroundColor Green
|
||||
@@ -158,4 +159,7 @@ Write-Host "Press any key to exit the script"
|
||||
[System.Console]::ReadKey($true) # Waits for a key press and doesn't display the pressed key
|
||||
|
||||
clear
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Applications Documentation](<../../../reference/Applications/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+8
-2
@@ -7,7 +7,10 @@ tags:
|
||||
- Windows
|
||||
---
|
||||
|
||||
``` batch
|
||||
## Purpose
|
||||
This document records the procedure for blue iris server watchdog. Follow the environment assumptions and commands below.
|
||||
|
||||
```batch
|
||||
@echo off
|
||||
|
||||
REM Change to the Blue Iris 5 directory
|
||||
@@ -28,4 +31,7 @@ timeout /t 10 /nobreak >nul
|
||||
|
||||
REM Go back to the beginning of the loop
|
||||
GOTO :LOOP
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Applications Documentation](<../../../reference/Applications/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
tags:
|
||||
- Applications
|
||||
- Scripts
|
||||
- Documentation
|
||||
---
|
||||
|
||||
# Applications
|
||||
## Purpose
|
||||
Find scripts for applications. Follow the subject guide to choose the relevant environment and connect this material to the other document types.
|
||||
|
||||
## Includes
|
||||
- Email
|
||||
- Files and Collaboration
|
||||
- Gaming and Media
|
||||
- Home Automation
|
||||
|
||||
## Follow the Subject
|
||||
[Applications](<../../reference/Applications/index.md>) explains the relationships and offers starting points for the documented tasks.
|
||||
@@ -5,11 +5,10 @@ tags:
|
||||
- Linux
|
||||
---
|
||||
|
||||
# Git Repo Updater (Script)
|
||||
## Purpose
|
||||
Standalone `repo_watcher.sh` script used by the Git Repo Updater container. This script clones or pulls one or more repositories and rsyncs them into destination paths.
|
||||
Standalone `repo_watcher.sh` script used by the Git Repo Updater container. This script clones or pulls one or more repositories and rsyncs them into destination paths.
|
||||
|
||||
For the containerized version and deployment details, see the [Git Repo Updater container doc](../../deployments/platforms/containerization/docker/custom-containers/git-repo-updater.md).
|
||||
For the containerized version and deployment details, see the [Git Repo Updater container doc](<../../../deployments/Containers/Docker/Git Repo Updater.md>).
|
||||
|
||||
## Script
|
||||
```sh
|
||||
@@ -67,3 +66,5 @@ while true; do
|
||||
done
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Gitea Workflows](<../../../reference/Automation/Gitea Configuration Delivery.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
tags:
|
||||
- Automation
|
||||
- Scripts
|
||||
- Documentation
|
||||
---
|
||||
|
||||
# Automation
|
||||
## Purpose
|
||||
Find scripts for automation. Follow the subject guide to choose the relevant environment and connect this material to the other document types.
|
||||
|
||||
## Includes
|
||||
- Gitea
|
||||
|
||||
## Follow the Subject
|
||||
[Automation](<../../reference/Automation/index.md>) explains the relationships and offers starting points for the documented tasks.
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
- SSH
|
||||
- Bash
|
||||
- Authentication
|
||||
- Scripting
|
||||
- Linux
|
||||
---
|
||||
|
||||
*Purpose*: Sometimes you need two linux computers to be able to talk to eachother without requiring a password. Passwordless SSH can be achieved by running the following commands:
|
||||
|
||||
!!! note "Non-Root Key Storage Considerations"
|
||||
When you generate SSH keys, they will be stored in a specific user's profile, the one currently executing the commands. If you want to have passwordless SSH, you would run the commands from a non-root user (e.g. `nicole`).
|
||||
|
||||
``` sh
|
||||
ssh-keygen # (1)
|
||||
ssh-copy-id -i /home/nicole/.ssh/id_rsa.pub nicole@192.168.3.18 # (2)
|
||||
ssh -i /home/nicole/.ssh/id_rsa nicole@192.168.3.18 # (3)
|
||||
```
|
||||
|
||||
1. Just leave all of the default options and do not put a password on the SSH key. )
|
||||
2. Change the directories to account for your given username, and change the destination to the user@IP corresponding to the remote server. You will be prompted to enter the password once to store the SSH public key on the remote computer.
|
||||
3. This command is to validate that everything worked. If the remote user is the same as the local user (e.g. `nicole`) then you dont need to add the `-i /home/nicole/.ssh/id_rsa` section to the SSH command.
|
||||
|
||||
!!! warning "Run before configuring Global SSH Infrastructure Key"
|
||||
There is a global automation that leverages a [Global Infrastructure Public SSH Key](https://git.bunny-lab.io/Infrastructure/LinuxServer_SSH_PublicKey). If this runs before you run the commands above, you will be unable to configure SSH key relationships and it will need to be done manually.
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
- Linux
|
||||
- Bash
|
||||
- Scripting
|
||||
---
|
||||
|
||||
``` sh
|
||||
xrandr --auto
|
||||
xrandr --setprovideroutputsource 4 0
|
||||
xrandr --output HDMI-1 --primary --mode 1920x1080 --rate 75.00 --output DVI-I-1-1 --mode 1920x1080 --rate 60.00 --right-of HDMI-1 --output -eDP-1 --off
|
||||
```
|
||||
@@ -1,27 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
- Bash
|
||||
- QEMU
|
||||
- Scripting
|
||||
- Linux
|
||||
---
|
||||
|
||||
**Purpose**:
|
||||
You may need to install the QEMU guest agent on linux VMs manually, while Windows-based devices work out-of-the-box after installing the VirtIO guest tools installer.
|
||||
|
||||
=== "Ubuntu Server"
|
||||
|
||||
```sh
|
||||
sudo su
|
||||
apt update
|
||||
apt install -y qemu-guest-agent
|
||||
systemctl enable --now qemu-guest-agent
|
||||
```
|
||||
|
||||
=== "Rocky Linux"
|
||||
|
||||
```sh
|
||||
sudo su
|
||||
dnf install -y qemu-guest-agent
|
||||
systemctl enable --now qemu-guest-agent
|
||||
```
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
- XRDP
|
||||
- Bash
|
||||
- Scripting
|
||||
- Linux
|
||||
---
|
||||
|
||||
**Purpose**:
|
||||
If you need to set up RDP access to a Linux environment, you will want to install XRDP. Once it is installed, you can leverage other tools such as Apache Guacamole to remotely connect to it.
|
||||
|
||||
```
|
||||
# Install and Start XRDP Service
|
||||
sudo dnf install epel-release -y
|
||||
sudo dnf install xrdp -y
|
||||
sudo systemctl enable --now xrdp
|
||||
|
||||
# Open Firewall Rules for RDP Traffic
|
||||
sudo firewall-cmd --permanent --add-port=3389/tcp
|
||||
sudo firewall-cmd --reload
|
||||
|
||||
# Configure Desktop Environment to Launch when you Login via RDP (Run as Non-Root User)
|
||||
# XFCE4 Desktop Environment
|
||||
echo "startxfce4" > ~/.Xclients
|
||||
chmod +x ~/.Xclients
|
||||
```
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
- RAID
|
||||
- Bash
|
||||
- Scripting
|
||||
- Linux
|
||||
---
|
||||
|
||||
https://www.digitalocean.com/community/tutorials/how-to-create-raid-arrays-with-mdadm-on-ubuntu-16-04
|
||||
``` sh
|
||||
sudo mdadm --grow /dev/md0 -l 5
|
||||
cat /proc/mdstat
|
||||
```
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
- Bash
|
||||
- Ports
|
||||
- Scripting
|
||||
- Linux
|
||||
---
|
||||
|
||||
**Purpose**:
|
||||
If you want to check if a certain TCP port is open on a server.
|
||||
|
||||
## Netcat Command
|
||||
``` sh
|
||||
netcat -z -n -v <IP ADDRESS> <PORT>
|
||||
```
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
- Bash
|
||||
- Time Sync
|
||||
- Scripting
|
||||
- Linux
|
||||
---
|
||||
|
||||
The commands outlined in this short document are meant to be a quick-reference for setting the timezone and date/time of a Linux-based server.
|
||||
|
||||
### Set Timezone:
|
||||
```sh
|
||||
sudo timedatectl set-timezone America/Denver
|
||||
```
|
||||
|
||||
### Set Time & Date
|
||||
```sh
|
||||
date -s "1 JAN 2025 03:30:00"
|
||||
```
|
||||
@@ -1,58 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
- Containers
|
||||
- Docker
|
||||
- Bash
|
||||
- Scripting
|
||||
- Linux
|
||||
---
|
||||
|
||||
**Purpose**:
|
||||
If you find that you need to migrate a container, along with any supporting files, permissions, etc from an old server to a new server, rsync helps make this as painless as possible.
|
||||
Be sure to perform the following steps to make sure that you can copy the container's files.
|
||||
|
||||
!!! warning
|
||||
You need to stop the running containers on the old server before copying their data over, otherwise the state of the data may be unstable. Once you have migrated the data, you can spin up the containers on the new server and confirm they work before deleting the data on the old server.
|
||||
|
||||
On the destination (new) server, the directory needs to exist and be writable via the person copying the data over SSH:
|
||||
|
||||
## Copying Data Between the Old and New Servers
|
||||
|
||||
=== "Safe Method"
|
||||
|
||||
``` sh
|
||||
sudo mkdir -p /srv/containers/example
|
||||
sudo chmod 740 /srv/containers/example
|
||||
sudo chown nicole:nicole /srv/containers/example
|
||||
```
|
||||
|
||||
=== "Quick & Dirty Method"
|
||||
|
||||
``` sh
|
||||
sudo mkdir -p /srv/containers
|
||||
sudo chmod 777 /srv/containers
|
||||
```
|
||||
|
||||
On the source (old) server, perform an rsync over to the new server, authenticating yourself as you will be prompted to do so:
|
||||
|
||||
=== "Safe Method"
|
||||
|
||||
``` sh
|
||||
rsync -avz -e ssh --progress /srv/containers/example/* nicole@192.168.3.30:/srv/containers/example
|
||||
```
|
||||
|
||||
=== "Quick & Dirty Method"
|
||||
|
||||
``` sh
|
||||
rsync -avz -e ssh --progress /srv/containers/example nicole@192.168.3.30:/srv/containers
|
||||
```
|
||||
|
||||
=== "Quick & Dirty w/ Provided SSH Key Method"
|
||||
This method assumes that you have the private key for your SSH-based authentication locally on the server somewhere safe with permissions `chmod 600` applied to it. In this example, I placed the private key at `/tmp/id_rsa_OpenSSH`.
|
||||
|
||||
``` sh
|
||||
rsync -avz -e "ssh -i /tmp/id_rsa_OpenSSH" --progress /srv/containers/pihole nicole@192.168.3.62:/srv/containers
|
||||
```
|
||||
|
||||
## Spinning Up Docker / Portainer Stack
|
||||
Once everything has been moved over, copy the `docker-compose` and `.env` (environment variables) from the old server to the new one, pointing to the same location since we maintained the same folder structure, and the container should spin up like nothing ever happened.
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
- Bash
|
||||
- Netcat
|
||||
- File Transfer
|
||||
- Scripting
|
||||
- Linux
|
||||
---
|
||||
|
||||
**Purpose**: You may find that you need to transfer a file, such as a public SSH key, or some other kind of file between two devices. In this scenario, we assume both devices have the `netcat` command available to them. By putting a network listener on the device recieving the file, then sending the file to that device's IP and port, you can successfully transfer data between computers without needing to set up SSH, FTP, or anything else to establish initial trust between the devices. [Original Reference Material](https://www.youtube.com/shorts/1j17UBGqSog).
|
||||
|
||||
!!! warning
|
||||
The data being transferred will not be encrypted. If you are transferring relatively-safe files such as public SSH keys, etc, this should be fine.
|
||||
|
||||
### Destination Computer
|
||||
Run the following command on the computer that will be recieving the file.
|
||||
``` sh
|
||||
netcat -l <random-port> > /tmp/OUTPUT-AS-FILE.txt
|
||||
```
|
||||
|
||||
### Source Computer
|
||||
Run the following command on the computer that will be sending the file to the destination computer.
|
||||
``` sh
|
||||
cat INPUT-DATA.txt | netcat <IP-of-Destination-Computer> <Port-of-Destination-Computer> -q 0
|
||||
```
|
||||
|
||||
!!! info
|
||||
The `-q 0` command argument causes the netcat connection to close itself automatically when the transfer is complete.
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
---
|
||||
tags:
|
||||
- Group Policy
|
||||
- PowerShell
|
||||
- Scripting
|
||||
---
|
||||
|
||||
## Purpose
|
||||
This document records the procedure for force group policy updates across the domain. Follow the environment assumptions and commands below.
|
||||
|
||||
```powershell
|
||||
$computers = Get-ADComputer -Filter * -SearchBase "OU=Computers,DC=bunny-lab,DC=io"
|
||||
$computers | ForEach-Object -Process {Invoke-GPUpdate -Computer $_.name -RandomDelayInMinutes 0 -Force}
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Identity and Certificates Documentation](<../../../reference/Identity and Certificates/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+6
-3
@@ -5,9 +5,12 @@ tags:
|
||||
- Scripting
|
||||
---
|
||||
|
||||
**Purpose**:
|
||||
## Purpose
|
||||
Sometimes you need a report of every user in a domain, and if/when their passwords will expire. This one-liner command will help automate that reporting.
|
||||
|
||||
``` powershell
|
||||
```powershell
|
||||
Get-Aduser -filter "enabled -eq 'true'" -properties passwordlastset, passwordneverexpires | ft Name, passwordlastset, Passwordneverexpires > C:\PWReport.txt
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Identity and Certificates Documentation](<../../../../reference/Identity and Certificates/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
tags:
|
||||
- PowerShell
|
||||
- Reporting
|
||||
- Scripting
|
||||
---
|
||||
|
||||
## Purpose
|
||||
This document records the procedure for inactive computers. Follow the environment assumptions and commands below.
|
||||
|
||||
```powershell
|
||||
$DaysInactive = 30
|
||||
$time = (Get-Date).Adddays(-($DaysInactive))
|
||||
Get-ADComputer -Filter {LastLogonTimeStamp -lt $time} -ResultPageSize 2000 -resultSetSize $null -Properties Name | Select Name
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Identity and Certificates Documentation](<../../../../reference/Identity and Certificates/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
tags:
|
||||
- PowerShell
|
||||
- Reporting
|
||||
- Scripting
|
||||
---
|
||||
|
||||
## Purpose
|
||||
This document records the procedure for inactive users. Follow the environment assumptions and commands below.
|
||||
|
||||
```powershell
|
||||
InactiveDays = 30
|
||||
$Days = (Get-Date).Adddays(-($InactiveDays))
|
||||
Get-ADUser -Filter {LastLogonTimeStamp -lt $Days -and enabled -eq $true} -Properties LastLogonTimeStamp |
|
||||
select-object Name,@{Name="Date"; Expression={[DateTime]::FromFileTime($_.lastLogonTimestamp).ToString('MM-dd-yyyy')}}
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Identity and Certificates Documentation](<../../../../reference/Identity and Certificates/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+10
-3
@@ -5,17 +5,24 @@ tags:
|
||||
- Scripting
|
||||
---
|
||||
|
||||
!!! info "Prerequesite: [Connect to Azure AD](./connect-to-azure-ad.md)"
|
||||
## Purpose
|
||||
This document records the procedure for check email aliases. Follow the environment assumptions and commands below.
|
||||
|
||||
!!! info "Prerequesite: [Connect to Azure AD](<Connect to Azure AD.md>)"
|
||||
|
||||
The uppercase `SMTP` address is the primary address, while lowercase `smtp` are aliases. You can find the value in active directory in **"User > Attribute Editor > proxyAddresses"**.
|
||||
``` powershell
|
||||
|
||||
```powershell
|
||||
Get-AzureADUser -ObjectId "user@domain.com" | Select -Property ProxyAddresses
|
||||
```
|
||||
|
||||
!!! example "Example Output"
|
||||
``` powershell
|
||||
|
||||
```powershell
|
||||
smtp:alias@domain.com
|
||||
smtp:alias@domain.onmicrosoft.com
|
||||
SMTP:primaryaddress@domain.com
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Identity and Certificates Documentation](<../../../reference/Identity and Certificates/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+11
-5
@@ -4,21 +4,27 @@ tags:
|
||||
- Scripting
|
||||
---
|
||||
|
||||
**Purpose**: Sometimes you will need to connect to Azure AD via powershell in order to perform troubleshooting / automation.
|
||||
## Purpose
|
||||
Sometimes you will need to connect to Azure AD via powershell in order to perform troubleshooting / automation.
|
||||
|
||||
## Update Nuget Package Manager
|
||||
``` powershell
|
||||
```powershell
|
||||
Install-PackageProvider -Name NuGet -Force -ForceBootstrap
|
||||
```
|
||||
|
||||
## Install AzureAD Powershell Modules
|
||||
You will need to install the modules for AzureAD before you can run the commands necessary for querying Azure.
|
||||
``` powershell
|
||||
|
||||
```powershell
|
||||
Install-Module -Name AzureAD
|
||||
```
|
||||
|
||||
## Connect to AzureAD
|
||||
When you run the following command, it will open a dialog box to take the username, password, and MFA code (if applicable) for an administrative account in the Azure Active Directory.
|
||||
``` powershell
|
||||
|
||||
```powershell
|
||||
Connect-AzureAD
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Identity and Certificates Documentation](<../../../reference/Identity and Certificates/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
tags:
|
||||
- Identity and Certificates
|
||||
- Scripts
|
||||
- Documentation
|
||||
---
|
||||
|
||||
# Identity and Certificates
|
||||
## Purpose
|
||||
Find scripts for identity and certificates. Follow the subject guide to choose the relevant environment and connect this material to the other document types.
|
||||
|
||||
## Includes
|
||||
- Active Directory
|
||||
- Microsoft 365
|
||||
|
||||
## Follow the Subject
|
||||
[Identity and Certificates](<../../reference/Identity and Certificates/index.md>) explains the relationships and offers starting points for the documented tasks.
|
||||
+11
-2
@@ -1,5 +1,11 @@
|
||||
---
|
||||
tags:
|
||||
- Networking and Access
|
||||
- Change DNS Client Server Settings Remotely
|
||||
---
|
||||
|
||||
## Purpose
|
||||
You may find that for one reason or another, you need to change DNS records of remote windows devices and cannot login via RDP or via console, yet somehow WinRM continues to work. In these scenarios, you can use the following commands to identify the network adapter, change its DNS servers, and verify the settings afterwards.
|
||||
Change the DNS server addresses used by the selected Windows network adapters through PowerShell remoting. This changes client resolver settings rather than records hosted in a DNS zone.
|
||||
|
||||
!!! info "Run as Domain Admin"
|
||||
You need to run the following commands within the context of a powershell session running as a domain admin, otherwise the `Invoke-Command` commands will fail to execute.
|
||||
@@ -16,4 +22,7 @@ Invoke-Command -ComputerName $DEVICE -ScriptBlock { Get-DnsClientServerAddress -
|
||||
|
||||
# Replace Current DNS Servers for the selected interface
|
||||
Invoke-Command -ComputerName $DEVICE -ScriptBlock { Set-DnsClientServerAddress -InterfaceAlias "Ethernet" -ServerAddresses ("192.168.3.25","192.168.3.26") }
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Networking and Access Documentation](<../../reference/Networking and Access/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
tags:
|
||||
- Bash
|
||||
- Ports
|
||||
- Scripting
|
||||
- Linux
|
||||
---
|
||||
|
||||
## Purpose
|
||||
If you want to check if a certain TCP port is open on a server.
|
||||
|
||||
## Netcat Command
|
||||
```sh
|
||||
netcat -z -n -v <IP ADDRESS> <PORT>
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Networking and Access Documentation](<../../reference/Networking and Access/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+4
-1
@@ -98,4 +98,7 @@ foreach ($adapter in $adapters) {
|
||||
}
|
||||
|
||||
Write-Host "DNS check and correction completed for adapters with a default gateway."
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Networking and Access Documentation](<../../reference/Networking and Access/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
tags:
|
||||
- Networking and Access
|
||||
- Scripts
|
||||
- Documentation
|
||||
---
|
||||
|
||||
# Networking and Access
|
||||
## Purpose
|
||||
Find scripts for networking and access. Follow the subject guide to choose the relevant environment and connect this material to the other document types.
|
||||
|
||||
## Includes
|
||||
- Change DNS Client Server Settings Remotely
|
||||
- Check Open Ports
|
||||
- Correct DNS Server Priority
|
||||
|
||||
## Follow the Subject
|
||||
[Networking and Access](<../../reference/Networking and Access/index.md>) explains the relationships and offers starting points for the documented tasks.
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
- Group Policy
|
||||
- PowerShell
|
||||
- Scripting
|
||||
---
|
||||
|
||||
``` powershell
|
||||
$computers = Get-ADComputer -Filter * -SearchBase "OU=Computers,DC=bunny-lab,DC=io"
|
||||
$computers | ForEach-Object -Process {Invoke-GPUpdate -Computer $_.name -RandomDelayInMinutes 0 -Force}
|
||||
```
|
||||
@@ -1,518 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
- Rclone
|
||||
- PowerShell
|
||||
- Synchronization
|
||||
- Google Drive
|
||||
---
|
||||
|
||||
## Purpose
|
||||
This document explains the practical differences between the rclone `copy`, `sync`, `check`, and `bisync` commands and provides a safety-first workflow for configuring and recovering a bidirectional synchronization pair. The examples use PowerShell and assume that one side of the synchronization is a Google Drive remote.
|
||||
|
||||
Rclone is a command-line file-management program that supports cloud storage providers, object-storage platforms, local filesystems, and standard transfer protocols.
|
||||
|
||||
[Download rClone](https://rclone.org/downloads)
|
||||
|
||||
[Official rClone Documentation](https://rclone.org/docs/)
|
||||
|
||||
!!! info "Version Context"
|
||||
This document was reviewed against rclone `v1.75.0`. Older releases may not support every bisync flag documented here, and future versions may change some recovery behavior.
|
||||
|
||||
!!! danger "Rclone Can Delete or Overwrite Data"
|
||||
The `sync` and `bisync` commands can delete, replace, rename, or propagate the deletion of files. Always confirm the source and destination paths, maintain a separate backup or snapshot, and preview unfamiliar operations with `--dry-run` before allowing them to modify data.
|
||||
|
||||
A dry run is a preview and is not a replacement for a backup.
|
||||
|
||||
## Command Behavior
|
||||
The correct command depends on the intended relationship between the source and destination.
|
||||
|
||||
| **Command** | **Primary Behavior** | **Deletes Destination-Only Files** | **Direction** |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `copy` | Adds or updates source files at the destination while retaining unrelated destination files | No | One-way |
|
||||
| `sync` | Makes the destination match the source | Yes | One-way |
|
||||
| `check` | Compares files without modifying either side | No | Read-only |
|
||||
| `bisync` | Detects and propagates changes made on either side by comparing the current state against prior listings | Yes | Two-way |
|
||||
|
||||
### Use `copy` for Additive Transfers
|
||||
Use `copy` when you need to add or update files without deleting files that already exist only at the destination.
|
||||
|
||||
```powershell
|
||||
& $Rclone copy "Source" "Destination" --update --dry-run --verbose
|
||||
```
|
||||
|
||||
The `--update` flag skips a source file when the corresponding destination file has a newer modification time. It does not create version history, and it does not prevent an older destination file from being replaced by a newer source file.
|
||||
|
||||
After reviewing the dry-run output, repeat the operation without `--dry-run`:
|
||||
|
||||
```powershell
|
||||
& $Rclone copy "Source" "Destination" --update --verbose
|
||||
```
|
||||
|
||||
### Use `sync` Only for Intentional Mirroring
|
||||
Use `sync` when the destination must become a one-way mirror of the source.
|
||||
|
||||
!!! danger "`sync` Deletes Destination-Only Files"
|
||||
The `sync` command removes files from the destination when they do not exist in the source. This is true even when `--update` is present.
|
||||
|
||||
The `--update` flag only prevents a newer destination file from being replaced by an older source file. It does not convert `sync` into an additive operation and does not protect destination-only files from deletion.
|
||||
|
||||
Preview the operation first:
|
||||
|
||||
```powershell
|
||||
& $Rclone sync "Source" "Destination" --dry-run --verbose
|
||||
```
|
||||
|
||||
Only remove `--dry-run` after confirming that every proposed copy, replacement, and deletion is intentional:
|
||||
|
||||
```powershell
|
||||
& $Rclone sync "Source" "Destination" --verbose
|
||||
```
|
||||
|
||||
### Use `check` for Read-Only Comparison
|
||||
The `check` command compares files on both sides without copying, replacing, or deleting them.
|
||||
|
||||
```powershell
|
||||
& $Rclone check "Source" "Destination" --combined "rclone-comparison.txt" --log-level INFO --log-file "rclone-check.log"
|
||||
```
|
||||
|
||||
The combined report uses the following symbols:
|
||||
|
||||
| **Symbol** | **Meaning** |
|
||||
| :--- | :--- |
|
||||
| `=` | The file exists on both sides and matches |
|
||||
| `+` | The file exists only in the source |
|
||||
| `-` | The file exists only in the destination |
|
||||
| `*` | The same path exists on both sides, but the files differ |
|
||||
| `!` | The file could not be read or compared |
|
||||
|
||||
The `check` command compares files but does not report missing empty directories.
|
||||
|
||||
## Understand Bisync State
|
||||
Bisync is a stateful two-way synchronization command. It retains listings of Path1 and Path2 from the prior successful run and compares those listings against the current state during the next run.
|
||||
|
||||
Bisync does not maintain a file-version archive or historical copies of every changed file. The listings record synchronization state and metadata needed to determine whether a file is new, changed, or deleted relative to the previous run.
|
||||
|
||||
!!! warning "Protect the Bisync Work Directory"
|
||||
Use a stable, explicit `--workdir` for every run of a given bisync pair. Do not change the work directory, reverse the order of Path1 and Path2, delete the listing files, or run the same pair with different filters without understanding that the existing state may no longer be valid.
|
||||
|
||||
On Windows, bisync otherwise stores its state beneath the profile of the account running rclone. This can cause scheduled and interactive executions to use different state directories when they run as different accounts.
|
||||
|
||||
## Define the Example Environment
|
||||
Replace the example values before running the commands in this document.
|
||||
|
||||
```powershell
|
||||
$Rclone = "C:\Path\To\rclone.exe"
|
||||
$LocalPath = "C:\Path\To\Local"
|
||||
$RemotePath = "GoogleDrive:Path/To/Remote"
|
||||
$WorkDir = "C:\Path\To\BisyncState"
|
||||
$LogDir = "C:\Path\To\Logs"
|
||||
$FilterFile = "C:\Path\To\bisync-filters.txt"
|
||||
```
|
||||
|
||||
Create the state and log directories:
|
||||
|
||||
```powershell
|
||||
New-Item -Path $WorkDir,$LogDir -ItemType Directory -Force | Out-Null
|
||||
```
|
||||
|
||||
Create a consistent filters file:
|
||||
|
||||
```text title="C:\Path\To\bisync-filters.txt"
|
||||
- *.lnk
|
||||
```
|
||||
|
||||
The `*.lnk` rule excludes Windows shortcut files at any depth beneath the synchronization root.
|
||||
|
||||
!!! warning "Filter Changes Require a Reviewed Rebaseline"
|
||||
Bisync compares current listings against prior listings. Adding, removing, or changing a filter can make previously tracked files disappear from the new listings and appear to have been deleted.
|
||||
|
||||
Treat filter changes as a state-changing event. Stop scheduled runs, review the new scope, and perform a dry-run resync before committing the change.
|
||||
|
||||
## Validate the Paths
|
||||
Confirm the installed rclone version:
|
||||
|
||||
```powershell
|
||||
& $Rclone version
|
||||
```
|
||||
|
||||
Confirm that the local root already exists:
|
||||
|
||||
```powershell
|
||||
Test-Path -LiteralPath $LocalPath
|
||||
```
|
||||
|
||||
Expected result:
|
||||
|
||||
```text
|
||||
True
|
||||
```
|
||||
|
||||
Confirm that the configured remote path is reachable:
|
||||
|
||||
```powershell
|
||||
& $Rclone lsd $RemotePath
|
||||
```
|
||||
|
||||
!!! warning "Do Not Automatically Create an Unexpected Root"
|
||||
Stop if either path is missing or points to an unexpected location. Automatically creating an empty root can conceal a path, authentication, drive-mount, or configuration failure and can cause bisync to interpret an entire data set as deleted.
|
||||
|
||||
## Configure the Access Check
|
||||
The `--check-access` flag verifies that matching files named `RCLONE_TEST` are visible in the same relative locations on both sides. This provides additional protection against an unavailable mount, incorrect remote root, or incomplete listing being interpreted as mass deletion.
|
||||
|
||||
Create the access-check file at the root of the local path and copy it to the root of the remote path:
|
||||
|
||||
```powershell
|
||||
$AccessFile = Join-Path $LocalPath "RCLONE_TEST"
|
||||
New-Item -Path $AccessFile -ItemType File -Force | Out-Null
|
||||
& $Rclone copyto $AccessFile "$RemotePath/RCLONE_TEST"
|
||||
```
|
||||
|
||||
Confirm that the file appears on both sides:
|
||||
|
||||
```powershell
|
||||
& $Rclone lsf $LocalPath --include "RCLONE_TEST"
|
||||
& $Rclone lsf $RemotePath --include "RCLONE_TEST"
|
||||
```
|
||||
|
||||
Expected result from both commands:
|
||||
|
||||
```text
|
||||
RCLONE_TEST
|
||||
```
|
||||
|
||||
!!! warning "Do Not Delete the Access-Check File"
|
||||
The `RCLONE_TEST` file must remain visible on both sides while `--check-access` is enabled. Bisync will abort when the file cannot be found.
|
||||
|
||||
## Audit the Existing Data
|
||||
Before establishing or rebuilding the bisync state, compare the current file sets without changing either side:
|
||||
|
||||
```powershell
|
||||
& $Rclone check $LocalPath $RemotePath --filter-from $FilterFile --drive-skip-gdocs --combined (Join-Path $LogDir "pre-bisync-check.txt") --log-level INFO --log-file (Join-Path $LogDir "pre-bisync-check.log")
|
||||
```
|
||||
|
||||
Review all source-only, destination-only, differing, and unreadable files before proceeding. Determine whether the expected recovery is to merge both sides, prefer one authoritative side, or preserve selected files manually.
|
||||
|
||||
## Initialize a Bisync Pair
|
||||
A new bisync pair requires an initial resync to establish its Path1 and Path2 listings. A preliminary `sync --update` operation is not required and can unnecessarily delete destination-only files.
|
||||
|
||||
!!! danger "Resync Is Not a Database-Only Repair"
|
||||
A resync reconciles the actual contents of both paths while rebuilding the listings.
|
||||
|
||||
Files that exist on only one side are copied to the other side. When the same relative path contains different files on both sides, `--resync-mode` selects one version as the winner and overwrites the other version.
|
||||
|
||||
The conflict flags used during normal bisync runs do not apply during a resync. A resync does not rename the losing version into a conflict copy.
|
||||
|
||||
### Select the Resync Policy
|
||||
Choose the resync policy according to which data is authoritative:
|
||||
|
||||
| **Mode** | **Use When** |
|
||||
| :--- | :--- |
|
||||
| `path1` | Path1 is authoritative and must win every same-path difference |
|
||||
| `path2` | Path2 is authoritative and must win every same-path difference |
|
||||
| `newer` | Modification times are trustworthy and the newest same-path version should win |
|
||||
| `larger` | File size is a more reliable winner than modification time |
|
||||
|
||||
A bare `--resync` is equivalent to `--resync-mode path1`. Do not use a bare `--resync` unless Path1 is intentionally authoritative.
|
||||
|
||||
The examples below use `--resync-mode newer`. This is appropriate only when both sides provide trustworthy modification times and the intended policy is to preserve the newer same-path file.
|
||||
|
||||
### Preview the Initial Resync
|
||||
Run the initial resync as a dry run:
|
||||
|
||||
```powershell
|
||||
& $Rclone bisync $LocalPath $RemotePath --workdir $WorkDir --resync-mode newer --create-empty-src-dirs --compare size,modtime,checksum --check-access --max-delete 0 --filters-file $FilterFile --drive-skip-gdocs --fix-case --dry-run --verbose --log-file (Join-Path $LogDir "bisync-resync-dry-run.log")
|
||||
```
|
||||
|
||||
Review the complete log for:
|
||||
|
||||
- Files copied from Path1 to Path2
|
||||
- Files copied from Path2 to Path1
|
||||
- Same-path files that would be replaced
|
||||
- Unexpected paths
|
||||
- Access or checksum errors
|
||||
- Proposed deletion behavior
|
||||
|
||||
!!! note "Dry-Run Deletion Messages"
|
||||
A bisync dry run may display confusing deletion messages because the simulated copy that would normally precede the deletion did not actually occur. Review the complete sequence rather than evaluating an isolated deletion line.
|
||||
|
||||
Do not dismiss an unexpected deletion unless the log clearly shows that it is a dry-run artifact associated with a preceding simulated copy.
|
||||
|
||||
The `--max-delete 0` setting blocks ordinary file deletions during this recovery run. It does not prevent a same-path losing version from being overwritten during resync, so a separate backup or snapshot remains required.
|
||||
|
||||
### Perform the Initial Resync
|
||||
After reviewing and approving the dry-run output, repeat the command without `--dry-run`:
|
||||
|
||||
```powershell
|
||||
& $Rclone bisync $LocalPath $RemotePath --workdir $WorkDir --resync-mode newer --create-empty-src-dirs --compare size,modtime,checksum --check-access --max-delete 0 --filters-file $FilterFile --drive-skip-gdocs --fix-case --verbose --log-file (Join-Path $LogDir "bisync-resync-live.log")
|
||||
```
|
||||
|
||||
A successful run should end with:
|
||||
|
||||
```text
|
||||
Bisync successful
|
||||
```
|
||||
|
||||
### Validate the Reconciled Data
|
||||
Compare both sides after the live resync:
|
||||
|
||||
```powershell
|
||||
& $Rclone check $LocalPath $RemotePath --filter-from $FilterFile --drive-skip-gdocs --combined (Join-Path $LogDir "post-resync-check.txt") --log-level INFO --log-file (Join-Path $LogDir "post-resync-check.log")
|
||||
```
|
||||
|
||||
The expected result is:
|
||||
|
||||
```text
|
||||
0 differences found
|
||||
```
|
||||
|
||||
## Run Normal Bisync Operations
|
||||
After the baseline has been established, all normal runs must omit `--resync` and `--resync-mode`.
|
||||
|
||||
Preview the first normal run:
|
||||
|
||||
```powershell
|
||||
& $Rclone bisync $LocalPath $RemotePath --workdir $WorkDir --create-empty-src-dirs --conflict-resolve newer --conflict-loser num --compare size,modtime,checksum --resilient --recover --max-lock 2m --check-access --max-delete 10 --filters-file $FilterFile --drive-skip-gdocs --fix-case --dry-run --log-level INFO --log-file (Join-Path $LogDir "bisync-normal-dry-run.log")
|
||||
```
|
||||
|
||||
A healthy unchanged pair should report:
|
||||
|
||||
```text
|
||||
No changes found
|
||||
Updating listings
|
||||
Bisync successful
|
||||
```
|
||||
|
||||
After reviewing the dry run, perform one controlled live run:
|
||||
|
||||
```powershell
|
||||
& $Rclone bisync $LocalPath $RemotePath --workdir $WorkDir --create-empty-src-dirs --conflict-resolve newer --conflict-loser num --compare size,modtime,checksum --resilient --recover --max-lock 2m --check-access --max-delete 10 --filters-file $FilterFile --drive-skip-gdocs --fix-case --log-level INFO --log-file (Join-Path $LogDir "bisync.log")
|
||||
```
|
||||
|
||||
This normal command can be placed into the scheduled automation after it has completed successfully under the same Windows account and execution context that will run the scheduled task.
|
||||
|
||||
## Understand the Normal Bisync Flags
|
||||
| **Flag** | **Behavior** |
|
||||
| :--- | :--- |
|
||||
| `--workdir` | Stores the prior Path1 and Path2 listings in an explicit, stable directory |
|
||||
| `--create-empty-src-dirs` | Synchronizes the creation and deletion of empty directories |
|
||||
| `--compare size,modtime,checksum` | Uses size, modification time, and checksums when determining file state |
|
||||
| `--conflict-resolve newer` | Prefers the newer file when the same file changed independently on both sides since the prior run |
|
||||
| `--conflict-loser num` | Preserves the losing conflict version under a numbered `.conflictN` name |
|
||||
| `--resilient` | Allows certain less-serious errors to be retried during future runs rather than immediately requiring resync |
|
||||
| `--recover` | Uses a backup listing to recover from some interrupted or ungracefully terminated runs |
|
||||
| `--max-lock 2m` | Allows a stale bisync lock to expire after two minutes |
|
||||
| `--check-access` | Aborts when matching `RCLONE_TEST` files cannot be found on both sides |
|
||||
| `--max-delete 10` | Aborts when more than 10 percent of tracked files appear deleted on either side |
|
||||
| `--filters-file` | Applies one consistent set of exclusions to both bisync listings |
|
||||
| `--drive-skip-gdocs` | Makes native Google Docs, Sheets, Slides, and other Google-native documents invisible to rclone |
|
||||
| `--fix-case` | Allows supported case-only filename corrections between filesystems |
|
||||
| `--log-level INFO` | Records normal operations, changes, warnings, and errors rather than errors alone |
|
||||
|
||||
### Understand Conflict Resolution
|
||||
A bisync conflict occurs when the same relative file is new or changed on both sides compared with the prior successful run and the current file contents are not identical.
|
||||
|
||||
The `--conflict-resolve newer` option does not mean that rclone blindly compares every file and always keeps whichever modification time is newest. It applies specifically to a detected two-sided conflict.
|
||||
|
||||
With `--conflict-loser num`, the winner retains the original filename and the losing version is preserved with a numbered conflict suffix, such as:
|
||||
|
||||
```text
|
||||
Document.docx
|
||||
Document.docx.conflict1
|
||||
```
|
||||
|
||||
This is safer than `--conflict-loser delete`, which permanently removes the losing conflict version.
|
||||
|
||||
### Understand the Deletion Limit
|
||||
The `--max-delete 10` value is a conservative example rather than a universal requirement. Select a threshold appropriate for the size of the data set and the organization's normal deletion patterns.
|
||||
|
||||
A large folder rename may appear as many deletions and many new files and can exceed the configured threshold.
|
||||
|
||||
!!! danger "Do Not Use `--force` as a Recurring Option"
|
||||
The `--force` flag bypasses the `--max-delete` safety check. Do not include it in a normal scheduled command.
|
||||
|
||||
When a legitimate change exceeds the threshold, stop the scheduled operation, review the proposed changes with `--dry-run`, and use a one-time explicitly approved threshold instead of permanently disabling the protection.
|
||||
|
||||
### Understand Google-Native Documents
|
||||
The `--drive-skip-gdocs` flag removes native Google documents from rclone listings. These objects are not downloaded, exported, compared, or synchronized while the flag is enabled.
|
||||
|
||||
This flag does not merely ignore local files with extensions such as `.gdoc` or `.gsheet`. It makes the corresponding native Google Drive objects effectively invisible to rclone.
|
||||
|
||||
Remove this flag only when Google-native documents must be exported and synchronized and the desired export formats have been deliberately configured. Changing this behavior on an established bisync pair requires a reviewed rebaseline because it changes which objects appear in the listings.
|
||||
|
||||
## Repair a Broken Bisync Pair
|
||||
A request for `--resync` does not automatically mean that the data is damaged. Bisync may be unable to locate or trust its prior state because the work directory changed, the command used different paths or filters, the execution account changed, or a prior run ended with a critical error.
|
||||
|
||||
### Stop Automated Runs
|
||||
Stop all scheduled or looping bisync processes before investigating or repairing the state. Confirm that another rclone process is not modifying either path or the bisync listings.
|
||||
|
||||
### Review the Logs
|
||||
Search the most recent log for the first critical error rather than relying only on the final resync message.
|
||||
|
||||
```powershell
|
||||
Get-Content (Join-Path $LogDir "bisync.log") -Tail 300
|
||||
```
|
||||
|
||||
Look for:
|
||||
|
||||
- Authentication or remote-access errors
|
||||
- Missing-path errors
|
||||
- Missing or invalid listing files
|
||||
- Access-check failures
|
||||
- Excessive deletion warnings
|
||||
- File-copy or file-move failures
|
||||
- Lock-file errors
|
||||
- Filter changes
|
||||
- `Bisync aborted`
|
||||
- `Bisync critical error`
|
||||
|
||||
### Verify the Existing Configuration
|
||||
Confirm that the repair command uses:
|
||||
|
||||
- The original Path1 and Path2 in the original order
|
||||
- The original `--workdir`
|
||||
- The original filter rules
|
||||
- The intended rclone configuration file
|
||||
- The correct remote account and storage location
|
||||
- The same Google Docs behavior
|
||||
- The same comparison settings
|
||||
|
||||
List the current bisync state directory:
|
||||
|
||||
```powershell
|
||||
Get-ChildItem -LiteralPath $WorkDir
|
||||
```
|
||||
|
||||
The directory should contain Path1 and Path2 `.lst` files for the configured pair.
|
||||
|
||||
### Test Remote and Local Access
|
||||
Confirm that both roots are present and readable:
|
||||
|
||||
```powershell
|
||||
Test-Path -LiteralPath $LocalPath
|
||||
& $Rclone lsd $RemotePath
|
||||
```
|
||||
|
||||
Confirm that the access-check file remains visible:
|
||||
|
||||
```powershell
|
||||
& $Rclone lsf $LocalPath --include "RCLONE_TEST"
|
||||
& $Rclone lsf $RemotePath --include "RCLONE_TEST"
|
||||
```
|
||||
|
||||
### Compare the Current Data
|
||||
Run a read-only comparison before deciding to resync:
|
||||
|
||||
```powershell
|
||||
& $Rclone check $LocalPath $RemotePath --filter-from $FilterFile --drive-skip-gdocs --combined (Join-Path $LogDir "repair-comparison.txt") --log-level INFO --log-file (Join-Path $LogDir "repair-check.log")
|
||||
```
|
||||
|
||||
Review whether one-sided files are expected, whether one side is authoritative, and whether same-path differences should be resolved manually.
|
||||
|
||||
### Attempt Normal Recovery When Appropriate
|
||||
When the prior run was only interrupted and the normal command already uses `--recover`, a normal controlled run may recover without requiring a resync.
|
||||
|
||||
Run it first as a dry run:
|
||||
|
||||
```powershell
|
||||
& $Rclone bisync $LocalPath $RemotePath --workdir $WorkDir --create-empty-src-dirs --conflict-resolve newer --conflict-loser num --compare size,modtime,checksum --resilient --recover --max-lock 2m --check-access --max-delete 10 --filters-file $FilterFile --drive-skip-gdocs --fix-case --dry-run --log-level INFO --log-file (Join-Path $LogDir "bisync-recovery-dry-run.log")
|
||||
```
|
||||
|
||||
Proceed with a live normal run only when the output is understood and expected.
|
||||
|
||||
### Rebuild the State Only When Required
|
||||
Use a resync only when:
|
||||
|
||||
- The pair has never been initialized
|
||||
- The listings are missing or cannot be trusted
|
||||
- A critical bisync error explicitly requires a resync
|
||||
- The path scope or filter rules intentionally changed
|
||||
- You are deliberately establishing a new authoritative baseline
|
||||
|
||||
Select the correct `--resync-mode`, perform a dry run, review every proposed change, and then follow the initialization and validation sequence documented above.
|
||||
|
||||
!!! danger "Do Not Describe Resync as Non-Destructive"
|
||||
Resync can overwrite a same-path file and can restore files that were intentionally deleted by copying one-sided files back to the other side.
|
||||
|
||||
The `--conflict-resolve` and `--conflict-loser` flags are ignored during resync. Maintain an independent backup and do not proceed until the chosen resync policy is understood.
|
||||
|
||||
## Validate Bidirectional Synchronization
|
||||
After the initial setup or a repair, validate both directions with disposable files.
|
||||
|
||||
- Create a test text file beneath the local root.
|
||||
- Run a normal bisync and confirm that the file appears remotely.
|
||||
- Create a second test text file beneath the remote root.
|
||||
- Run another normal bisync and confirm that the file appears locally.
|
||||
- Delete both test files.
|
||||
- Run another normal bisync and confirm that the deletions propagate as intended.
|
||||
- Confirm that the final run ends with `Bisync successful`.
|
||||
- Run `rclone check` and confirm that no file differences remain.
|
||||
|
||||
Remove all test artifacts when validation is complete.
|
||||
|
||||
## Troubleshooting
|
||||
### Google Drive Reports Shared Drive Not Found
|
||||
An error such as `404: Shared Drive not found` normally means that the authenticated Google account cannot access the configured Shared Drive ID.
|
||||
|
||||
Verify:
|
||||
|
||||
- The correct Google account completed OAuth
|
||||
- The account still has access to the Shared Drive
|
||||
- The configured Shared Drive ID is correct
|
||||
- The Shared Drive was not deleted and recreated under a new ID
|
||||
|
||||
Do not replace the drive ID with another visible Shared Drive merely because authentication succeeded.
|
||||
|
||||
### Bisync Cannot Find Its Listings
|
||||
Confirm that the command uses the original `--workdir` and that the scheduled task runs under the expected account.
|
||||
|
||||
Do not perform an immediate resync when the actual problem is that rclone is looking in the wrong state directory.
|
||||
|
||||
### The Deletion Limit Was Exceeded
|
||||
Stop the operation and determine why so many files appear deleted. Common causes include:
|
||||
|
||||
- An unavailable local mount
|
||||
- An inaccessible remote path
|
||||
- An incorrect synchronization root
|
||||
- A large folder rename
|
||||
- Changed filter rules
|
||||
- An actual mass deletion
|
||||
|
||||
Do not use `--force` until the reported deletions have been independently reviewed and approved.
|
||||
|
||||
### Conflict Files Are Appearing
|
||||
Files ending in `.conflict1`, `.conflict2`, or another numbered suffix indicate that both sides changed independently and bisync preserved the losing version.
|
||||
|
||||
Review the contents, retain the correct version, and remove the obsolete conflict copy after confirming that it is no longer needed.
|
||||
|
||||
### Google Docs Are Missing
|
||||
Native Google Docs are intentionally absent when `--drive-skip-gdocs` is enabled. Remove or change this behavior only as a deliberate configuration change with a reviewed resync.
|
||||
|
||||
### Dry Run Appears to Delete a Newly Copied File
|
||||
Review the complete dry-run sequence. Bisync can display an apparent deletion because the preceding simulated copy did not actually create the file on the other side.
|
||||
|
||||
Do not ignore unrelated or unexplained deletion messages.
|
||||
|
||||
### Google Drive Reports Duplicate Objects
|
||||
Google Drive can contain multiple objects with the same name in one folder. List duplicate names with:
|
||||
|
||||
```powershell
|
||||
& $Rclone dedupe list $RemotePath
|
||||
```
|
||||
|
||||
Use the interactive resolver only after reviewing the file sizes, modification times, and hashes:
|
||||
|
||||
```powershell
|
||||
& $Rclone dedupe interactive $RemotePath
|
||||
```
|
||||
|
||||
When the correct version cannot be determined confidently, rename and preserve both objects rather than deleting one automatically.
|
||||
|
||||
## Reference Documentation
|
||||
- [Rclone Command Overview](https://rclone.org/commands/)
|
||||
- [Rclone Bisync](https://rclone.org/bisync/)
|
||||
- [Rclone Copy](https://rclone.org/commands/rclone_copy/)
|
||||
- [Rclone Sync](https://rclone.org/commands/rclone_sync/)
|
||||
- [Rclone Check](https://rclone.org/commands/rclone_check/)
|
||||
- [Rclone Filtering](https://rclone.org/filtering/)
|
||||
- [Rclone Google Drive Backend](https://rclone.org/drive/)
|
||||
- [Rclone Changelog](https://rclone.org/changelog/)
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
- PowerShell
|
||||
- Reporting
|
||||
- Scripting
|
||||
---
|
||||
|
||||
``` powershell
|
||||
$DaysInactive = 30
|
||||
$time = (Get-Date).Adddays(-($DaysInactive))
|
||||
Get-ADComputer -Filter {LastLogonTimeStamp -lt $time} -ResultPageSize 2000 -resultSetSize $null -Properties Name | Select Name
|
||||
```
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
- PowerShell
|
||||
- Reporting
|
||||
- Scripting
|
||||
---
|
||||
|
||||
``` powershell
|
||||
InactiveDays = 30
|
||||
$Days = (Get-Date).Adddays(-($InactiveDays))
|
||||
Get-ADUser -Filter {LastLogonTimeStamp -lt $Days -and enabled -eq $true} -Properties LastLogonTimeStamp |
|
||||
select-object Name,@{Name="Date"; Expression={[DateTime]::FromFileTime($_.lastLogonTimestamp).ToString('MM-dd-yyyy')}}
|
||||
```
|
||||
@@ -1,78 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
- Microsoft Exchange
|
||||
- Email
|
||||
---
|
||||
|
||||
## Purpose
|
||||
If you operate an Exchange Database Availability Group (DAG) with 2 or more servers, you may need to do maintenance to one of the members, and during that maintenance, it's possible that one of the databases of the server that was rebooted etc will be out-of-date. In case this happens, it may suspend the database replication to one of the DAG's member servers.
|
||||
|
||||
## Checking DAG Database Replication Status
|
||||
You will want to first log into one of the DAG servers and open the *"Exchange Management Shell"*. From there, run the following command to get the status of database replication. An example of the kind of output you would see is below the command.
|
||||
|
||||
```powershell
|
||||
Get-MailboxDatabaseCopyStatus * | Format-Table Name, Status, CopyQueueLength, ReplayQueueLength, ContentIndexState
|
||||
```
|
||||
|
||||
| **Name** | **Status** | **CopyQueueLength** | **ReplayQueueLength** | **ContentIndexState** |
|
||||
| :--- | ---: | ---: | ---: | ---: |
|
||||
| DB01\MX-DAG-01 | Mounted | 0 | 0 | Healthy |
|
||||
| DB01\MX-DAG-02 | Healthy | 0 | 0 | Healthy |
|
||||
|
||||
!!! info "Example Output Breakdown"
|
||||
In the above example output, you can see that there are two member servers in the DAG, `MX-DAG-01` and `MX-DAG-02`. Then you will see that there is a status of `Mounted`, this means that `MX-DAG-01` is the active production server; this means that it is handling all mailflow and web requests / webmail.
|
||||
|
||||
**CopyQueueLength**: This is a number of database "*transaction logs*" that have taken place since a replica database stopped getting updates. This is the queue of all database transactions that are being copied from the production (mounted) database to replica databases. This data is not immediately written to the replica database(s).
|
||||
|
||||
**CopyReplayLength**: This represents the queue of all data that was successfully copied from the production database to the replica database on the given DAG member that still needs to process on the replica database. The "**CopyQueueLength**" will need to reach zero before the "**CopyReplayLength**" will start making meaningful progress to reaching zero.
|
||||
|
||||
When both the "**CopyQueueLength**" and "**CopyReplayLength**" queues have reached zero, the replica database(s) will have reached 100% parity with the production (active/mounted) database.
|
||||
|
||||
## Changing Active/Mounted DAG Member
|
||||
You may find that you need to perform work on one of the DAG members, and that requires you to failover the responsibility of hosting the Exchange environment to one of the other members of the DAG. You can generally do this with one command, seen below:
|
||||
```powershell
|
||||
Move-ActiveMailboxDatabase -Identity "DB01" -ActivateOnServer "MX-DAG-02" -MountDialOverride BestAvailability
|
||||
```
|
||||
|
||||
!!! info "Argument Breakdown"
|
||||
`-MountDialOverride`
|
||||
Specifies how tolerant Exchange should be to database copy health when mounting a database on the target server. This setting controls the level of availability Exchange requires before mounting the mailbox database after the move.
|
||||
|
||||
`-MountDialOverride`
|
||||
Instructs Exchange to mount the database as long as at least one healthy copy is available. This option maximizes uptime by allowing a database to mount even if some copies are unhealthy, prioritizing availability over strict health checks.
|
||||
|
||||
## Troubleshooting
|
||||
You may run into issues where either the `Status` or `ContentIndexState` are either Unhealthy, Suspended, or Failed. If this happens, you need to resume replication of the database from the production active/mounted server to the server that is having issues. In the worst-case, you would re-seed the replica database from-scratch.
|
||||
|
||||
### If `Status` is Unhealthy or Suspended
|
||||
If one of the DAG members has a status of "**Unhealthy**", you can run the following command to attempt to resume replication.
|
||||
```powershell
|
||||
Resume-MailboxDatabaseCopy -Identity "DB01\MX-DAG-02"
|
||||
```
|
||||
|
||||
If this fails to cause replication to resume, you can try telling the database to just focus on replication, which tells it to copy the queues and replay them on the replica database, while avoiding interacting with the "**ContentIndexState**" which can be individually fixed in the commands below:
|
||||
```powershell
|
||||
Resume-MailboxDatabaseCopy -Identity "DB01\MX-DAG-02" -ReplicationOnly
|
||||
```
|
||||
|
||||
### If `Status` is `ServiceDown`
|
||||
If you see this, it generally means that the Exchange Services for some reason or another are not running. You can remediate this with a powershell script. You will then have to double-check your work to ensure that all "Microsoft Exchange" services that have a startup mode of "Automatic" are running, if not, manually start them, then check on the status of the DAG again to see if the status changes from `ServiceDown` to `Healthy`. Depending on the speed of the Exchange server, it may take a few minutes, 5-10 minutes, for the services to fully initialize and be ready to handle requests. Go get a coffee and come back and check on the status of the DAG at that time.
|
||||
|
||||
[:material-powershell: Restart Exchange Services Script](../restart-exchange-services.md){ .md-button }
|
||||
|
||||
### If `ContentIndexState` is Unhealthy or Suspended
|
||||
If you see that the "ContentIndexState" is unhappy, you can run the following command to force it to re-seed / rebuild itself. (This is non-destructive this this is happening on a replica database).
|
||||
```powershell
|
||||
Update-MailboxDatabaseCopy "DB01\MX05" -CatalogOnly -BeginSeed
|
||||
```
|
||||
|
||||
### If Replica Database is FUBAR
|
||||
If the replica database just is not playing nice, you can take the *nuclear option* of completely rebuilding the replica database.
|
||||
|
||||
!!! warning
|
||||
This will destroy the replica database, so be careful to ensure you have a backup (if possible) before you do this. The following command will completely replace the replica database and replicate the data from the production active/mounted database to the newly-created replica database.
|
||||
```powershell
|
||||
Update-MailboxDatabaseCopy -Identity "DB01\MX-DAG-02" -SourceServer "MX-DAG-01"
|
||||
```
|
||||
|
||||
|
||||
-930
@@ -1,930 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
- Exchange Server
|
||||
- Database Availability Group
|
||||
- Maintenance
|
||||
- Windows Server
|
||||
- PowerShell
|
||||
---
|
||||
|
||||
## Purpose
|
||||
This workflow applies Exchange Server Subscription Edition (SE), Windows Server, and approved prerequisite updates to a three-member database availability group (DAG) by updating one DAG member at a time. The procedure drains client and transport activity, moves active mailbox databases, places the target member into maintenance mode, installs updates, validates the updated member, and restores the intended database placement before the next cycle begins.
|
||||
|
||||
All organization names, hostnames, FQDNs, DAG names, database names, and example values in this page describe the fictional Bunny Lab environment. The examples use the `bunny-lab.io` DNS namespace and do not identify another organization.
|
||||
|
||||
!!! warning "Update One DAG Member at a Time"
|
||||
Only one DAG member may be in maintenance mode at a time. Do not begin the next cycle until the previous member has returned to service, all database copies are healthy, all copy and replay queues have drained, transport queues are clear, and replication health passes across the entire DAG.
|
||||
|
||||
## Assumptions and Risk Boundaries
|
||||
- The Exchange organization is running Exchange Server SE on three Mailbox servers in one DAG.
|
||||
- Every mailbox database has at least two healthy passive copies before maintenance begins.
|
||||
- A current backup and a tested Exchange recovery path exist. DAG replication provides availability, but it is not a substitute for backup.
|
||||
- The operator has the Exchange and local administrative permissions required by the selected update. A CU that extends the schema or prepares Active Directory may require additional directory permissions before the first server is upgraded.
|
||||
- The administrative shell host remains online and is not the current maintenance target.
|
||||
- The Exchange DAG maintenance scripts are available through `$ExScripts`, and the administrative shell host has the Failover Clustering management tools installed.
|
||||
- The current Exchange release notes, prerequisites, known issues, and update-specific manual actions have been reviewed.
|
||||
- The required Exchange update media and Windows updates are approved and staged before the maintenance window begins.
|
||||
- Any load balancer, monitoring platform, backup platform, mail gateway, or third-party application that targets an individual Exchange server has an established drain and restore procedure.
|
||||
- All DAG members are returned to the same Exchange CU, SU, and HU level during the rolling update window.
|
||||
|
||||
!!! danger "Do Not Use a Snapshot as the Only Recovery Plan"
|
||||
Do not begin the rolling update without an Exchange-aware backup and documented recovery method. If an update fails, keep the affected server isolated in maintenance mode and repair that server before continuing to another DAG member.
|
||||
|
||||
## Example Bunny Lab Environment
|
||||
### Exchange Topology
|
||||
| **Object** | **Example Value** |
|
||||
| :--- | :--- |
|
||||
| Organization | `Bunny Lab` |
|
||||
| Active Directory DNS domain | `bunny-lab.io` |
|
||||
| DAG | `BL-DAG-01` |
|
||||
| Exchange version | Exchange Server Subscription Edition |
|
||||
| Update staging directory | `C:\ExchangeUpdates` |
|
||||
| Support scripts directory | `C:\Scripts` |
|
||||
|
||||
### DAG Members
|
||||
| **Short Name** | **FQDN** | **Normal Role** |
|
||||
| :--- | :--- | :--- |
|
||||
| `EXCH-SE-01` | `EXCH-SE-01.bunny-lab.io` | DAG member and primary administrative shell host |
|
||||
| `EXCH-SE-02` | `EXCH-SE-02.bunny-lab.io` | DAG member and alternate administrative shell host |
|
||||
| `EXCH-SE-03` | `EXCH-SE-03.bunny-lab.io` | DAG member |
|
||||
|
||||
Exchange cmdlets in this page normally use the Exchange server object name, such as `EXCH-SE-01`. Commands that require an FQDN, including `Redirect-Message -Target`, use the corresponding `bunny-lab.io` FQDN.
|
||||
|
||||
### Intended Database Placement
|
||||
| **Active Database** | **Intended Active Server** | **Passive Copy Servers** |
|
||||
| :--- | :--- | :--- |
|
||||
| `BL-MBX-01` | `EXCH-SE-01` | `EXCH-SE-02`, `EXCH-SE-03` |
|
||||
| `BL-ARC-01` | `EXCH-SE-01` | `EXCH-SE-02`, `EXCH-SE-03` |
|
||||
| `BL-MBX-02` | `EXCH-SE-02` | `EXCH-SE-01`, `EXCH-SE-03` |
|
||||
| `BL-ARC-02` | `EXCH-SE-02` | `EXCH-SE-01`, `EXCH-SE-03` |
|
||||
| `BL-MBX-03` | `EXCH-SE-03` | `EXCH-SE-01`, `EXCH-SE-02` |
|
||||
|
||||
### Rolling Upgrade Plan
|
||||
| **Cycle** | **Administrative Shell Host** | **Maintenance Target** | **Transport Redirect Target** | **Temporary Database Placement** |
|
||||
| ---: | :--- | :--- | :--- | :--- |
|
||||
| `1` | `EXCH-SE-01` | `EXCH-SE-03` | `EXCH-SE-01.bunny-lab.io` | `BL-MBX-03` to `EXCH-SE-01` |
|
||||
| `2` | `EXCH-SE-01` | `EXCH-SE-02` | `EXCH-SE-03.bunny-lab.io` | `BL-MBX-02` to `EXCH-SE-01`; `BL-ARC-02` to `EXCH-SE-03` |
|
||||
| `3` | `EXCH-SE-02` | `EXCH-SE-01` | `EXCH-SE-03.bunny-lab.io` | `BL-MBX-01` to `EXCH-SE-02`; `BL-ARC-01` to `EXCH-SE-03` |
|
||||
|
||||
This order is specific to the fictional topology above. In another environment, choose an order that preserves quorum, keeps an administrative shell host available, and distributes active databases across healthy remaining members.
|
||||
|
||||
## Prepare the Exchange Updates
|
||||
### Determine the Required Exchange Build
|
||||
From Exchange Management Shell on a healthy DAG member, record the base Exchange build stored in Active Directory:
|
||||
|
||||
```powershell
|
||||
$DagMembers = @("EXCH-SE-01", "EXCH-SE-02", "EXCH-SE-03")
|
||||
|
||||
$DagMembers | ForEach-Object {
|
||||
Get-ExchangeServer -Identity $_
|
||||
} | Format-Table Name, Edition, AdminDisplayVersion -Auto
|
||||
```
|
||||
|
||||
`AdminDisplayVersion` identifies the base release or CU, but it does not reliably identify the installed SU or HU. Query the local `ExSetup.exe` file on every DAG member to record the full installed file version:
|
||||
|
||||
```powershell
|
||||
Invoke-Command -ComputerName $DagMembers -ScriptBlock {
|
||||
$VersionInfo = (Get-Item (Join-Path $env:ExchangeInstallPath "bin\ExSetup.exe")).VersionInfo
|
||||
|
||||
[PSCustomObject]@{
|
||||
Server = $env:COMPUTERNAME
|
||||
ProductVersion = $VersionInfo.ProductVersion
|
||||
FileVersion = $VersionInfo.FileVersion
|
||||
}
|
||||
} | Format-Table -Auto
|
||||
```
|
||||
|
||||
Compare the results with the current Microsoft build table and the release article for the intended update. Install the latest supported CU required for the target release, then install the latest applicable SU or HU according to that release article. Do not install an SU or HU that was built for a different CU.
|
||||
|
||||
!!! warning "Release-Specific Instructions Take Precedence"
|
||||
Review the selected update's prerequisites, Active Directory preparation requirements, known issues, and manual post-installation actions before changing the first DAG member. This generic workflow does not replace release-specific Microsoft instructions.
|
||||
|
||||
### Run the Exchange Health Checker
|
||||
Download and stage the current Microsoft Exchange Health Checker script before the maintenance window. From an elevated PowerShell session on the administrative shell host, run it against every DAG member:
|
||||
|
||||
```powershell
|
||||
$DagMembers | ForEach-Object {
|
||||
& "C:\Scripts\HealthChecker.ps1" -Server $_
|
||||
}
|
||||
```
|
||||
|
||||
Resolve update-blocking findings before continuing. Preserve the generated reports with the maintenance record so the pre-update and post-update states can be compared.
|
||||
|
||||
### Stage Update Media
|
||||
Create the local staging directory on every DAG member:
|
||||
|
||||
```powershell
|
||||
Invoke-Command -ComputerName $DagMembers -ScriptBlock {
|
||||
New-Item -Path "C:\ExchangeUpdates" -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
```
|
||||
|
||||
Copy the approved Exchange CU media, SU or HU package, prerequisite installers, and any required scripts to `C:\ExchangeUpdates` on every DAG member. Use the exact package linked by the applicable Microsoft release article and verify that the file is complete before the maintenance window begins.
|
||||
|
||||
## Select the Current Upgrade Cycle
|
||||
Set these variables in Exchange Management Shell on the administrative shell host before running the common workflow. Run only the block for the current cycle.
|
||||
|
||||
### Cycle 1: Update `EXCH-SE-03`
|
||||
Run from Exchange Management Shell on `EXCH-SE-01`:
|
||||
|
||||
```powershell
|
||||
$DagName = "BL-DAG-01"
|
||||
$DagMembers = @("EXCH-SE-01", "EXCH-SE-02", "EXCH-SE-03")
|
||||
$AdminHost = "EXCH-SE-01"
|
||||
$TargetServer = "EXCH-SE-03"
|
||||
$RedirectTargetFqdn = "EXCH-SE-01.bunny-lab.io"
|
||||
```
|
||||
|
||||
### Cycle 2: Update `EXCH-SE-02`
|
||||
Run from Exchange Management Shell on `EXCH-SE-01` after Cycle 1 is fully validated:
|
||||
|
||||
```powershell
|
||||
$DagName = "BL-DAG-01"
|
||||
$DagMembers = @("EXCH-SE-01", "EXCH-SE-02", "EXCH-SE-03")
|
||||
$AdminHost = "EXCH-SE-01"
|
||||
$TargetServer = "EXCH-SE-02"
|
||||
$RedirectTargetFqdn = "EXCH-SE-03.bunny-lab.io"
|
||||
```
|
||||
|
||||
### Cycle 3: Update `EXCH-SE-01`
|
||||
Run from Exchange Management Shell on `EXCH-SE-02` after Cycle 2 is fully validated:
|
||||
|
||||
```powershell
|
||||
$DagName = "BL-DAG-01"
|
||||
$DagMembers = @("EXCH-SE-01", "EXCH-SE-02", "EXCH-SE-03")
|
||||
$AdminHost = "EXCH-SE-02"
|
||||
$TargetServer = "EXCH-SE-01"
|
||||
$RedirectTargetFqdn = "EXCH-SE-03.bunny-lab.io"
|
||||
```
|
||||
|
||||
Confirm that the current Exchange Management Shell session is running on `$AdminHost` and that `$AdminHost` is not equal to `$TargetServer`:
|
||||
|
||||
```powershell
|
||||
[PSCustomObject]@{
|
||||
CurrentComputer = $env:COMPUTERNAME
|
||||
AdminHost = $AdminHost
|
||||
TargetServer = $TargetServer
|
||||
}
|
||||
```
|
||||
|
||||
Do not continue if `CurrentComputer` does not match `AdminHost`, or if the administrative shell host is not fully healthy.
|
||||
|
||||
### Capture a Pre-Update Service Baseline
|
||||
Capture the Exchange-related service state on each server before the first maintenance action. Run this block once for the current target server from an elevated PowerShell session:
|
||||
|
||||
```powershell
|
||||
Invoke-Command -ComputerName $TargetServer -ScriptBlock {
|
||||
Get-CimInstance Win32_Service |
|
||||
Where-Object { $_.Name -like "MSExchange*" -or $_.Name -eq "FMS" } |
|
||||
Select-Object Name, DisplayName, State, StartMode, ExitCode |
|
||||
Export-Csv -Path "C:\ExchangeUpdates\PreUpdate-Services.csv" -NoTypeInformation
|
||||
}
|
||||
```
|
||||
|
||||
Do not replace this baseline with a service list copied from another organization. Exchange service startup modes can differ because of installed roles, enabled protocols, product version, and local design decisions.
|
||||
|
||||
## Validate DAG Health Before Each Cycle
|
||||
### Check Database Copy Health
|
||||
From the administrative shell host, check every database copy:
|
||||
|
||||
```powershell
|
||||
Get-MailboxDatabaseCopyStatus * |
|
||||
Sort-Object Name |
|
||||
Format-Table Name, Status, CopyQueueLength, ReplayQueueLength, ContentIndexState -Auto
|
||||
```
|
||||
|
||||
The preflight passes only when:
|
||||
|
||||
- Every active copy reports `Mounted`
|
||||
- Every passive copy reports `Healthy`
|
||||
- No copy reports `Failed`, `Suspended`, `Disconnected`, `ServiceDown`, or another unresolved failure state
|
||||
- Every copy queue is `0`
|
||||
- Every replay queue is `0`, or is low and demonstrably draining before any state-changing action
|
||||
- `ContentIndexState` matches the expected Exchange SE state; `NotApplicable` is normal for the BigFunnel search architecture and is not, by itself, a failure
|
||||
|
||||
### Check Replication Health
|
||||
Run replication health against every DAG member:
|
||||
|
||||
```powershell
|
||||
$DagMembers | ForEach-Object {
|
||||
Test-ReplicationHealth -Identity $_
|
||||
}
|
||||
```
|
||||
|
||||
Every applicable check must return `Passed`, and the `Error` field must be blank. Investigate any failure before moving a database or entering maintenance mode.
|
||||
|
||||
### Check Cluster State and Primary Active Manager
|
||||
Confirm every cluster node is up and identify the current Primary Active Manager (PAM):
|
||||
|
||||
```powershell
|
||||
Get-ClusterNode | Format-Table Name, State, NodeWeight, DynamicWeight -Auto
|
||||
|
||||
Get-DatabaseAvailabilityGroup -Identity $DagName -Status |
|
||||
Format-List Name, PrimaryActiveManager, OperationalServers
|
||||
```
|
||||
|
||||
All DAG members must be operational before the cycle begins. The maintenance script will move critical DAG functionality away from the target and pause its cluster node.
|
||||
|
||||
### Check Exchange Service Health
|
||||
Check Exchange service health on the target and the administrative shell host:
|
||||
|
||||
```powershell
|
||||
Test-ServiceHealth -Server $TargetServer
|
||||
Test-ServiceHealth -Server $AdminHost
|
||||
```
|
||||
|
||||
Resolve any required service failure before continuing.
|
||||
|
||||
### Check Active Database Placement
|
||||
List the currently mounted copies:
|
||||
|
||||
```powershell
|
||||
Get-MailboxDatabaseCopyStatus * |
|
||||
Where-Object { $_.Status -eq "Mounted" } |
|
||||
Sort-Object ActiveDatabaseCopy, Name |
|
||||
Format-Table Name, Status, ActiveDatabaseCopy, CopyQueueLength, ReplayQueueLength -Auto
|
||||
```
|
||||
|
||||
Compare the result with the intended placement table. A database may be temporarily active on another healthy member because of an earlier event, but its current state and all candidate copies must be understood before the maintenance move begins.
|
||||
|
||||
### Check Transport Queues
|
||||
Inspect transport queues on the target before draining it:
|
||||
|
||||
```powershell
|
||||
Get-Queue -Server $TargetServer |
|
||||
Sort-Object MessageCount -Descending |
|
||||
Format-Table Identity, Status, MessageCount, NextHopDomain -Auto
|
||||
```
|
||||
|
||||
Investigate growing, retrying, or unreachable queues before maintenance. Redirecting a queue does not correct an underlying transport or name-resolution failure.
|
||||
|
||||
!!! warning "Preflight Stop Conditions"
|
||||
Stop the cycle if any database copy is failed or suspended, replication health does not pass, a required Exchange service is unavailable, a cluster node is down, quorum is at risk, transport queues are persistently growing, the update prerequisites are unresolved, or the recovery path is unavailable.
|
||||
|
||||
## Move Active Databases Away From the Target
|
||||
### Confirm Candidate Copies
|
||||
Before each move, confirm that the chosen destination server holds a healthy passive copy with drained queues:
|
||||
|
||||
```powershell
|
||||
Get-MailboxDatabaseCopyStatus * |
|
||||
Sort-Object Name |
|
||||
Format-Table Name, Status, CopyQueueLength, ReplayQueueLength, ActiveDatabaseCopy -Auto
|
||||
```
|
||||
|
||||
Confirm the selected destination copy reports `Healthy` with `CopyQueueLength` and `ReplayQueueLength` equal to `0` before moving the active database.
|
||||
|
||||
!!! warning "Run Only the Current Cycle's Move Block"
|
||||
The following move blocks are cycle-specific. Do not run move commands for a different maintenance target.
|
||||
|
||||
### Cycle 1 Database Move
|
||||
Move `BL-MBX-03` from `EXCH-SE-03` to `EXCH-SE-01`:
|
||||
|
||||
```powershell
|
||||
Move-ActiveMailboxDatabase -Identity "BL-MBX-03" -ActivateOnServer "EXCH-SE-01" -Confirm:$false
|
||||
```
|
||||
|
||||
### Cycle 2 Database Moves
|
||||
Distribute the two active databases from `EXCH-SE-02` across the remaining healthy members:
|
||||
|
||||
```powershell
|
||||
Move-ActiveMailboxDatabase -Identity "BL-MBX-02" -ActivateOnServer "EXCH-SE-01" -Confirm:$false
|
||||
Move-ActiveMailboxDatabase -Identity "BL-ARC-02" -ActivateOnServer "EXCH-SE-03" -Confirm:$false
|
||||
```
|
||||
|
||||
### Cycle 3 Database Moves
|
||||
Distribute the two active databases from `EXCH-SE-01` across the remaining healthy members:
|
||||
|
||||
```powershell
|
||||
Move-ActiveMailboxDatabase -Identity "BL-MBX-01" -ActivateOnServer "EXCH-SE-02" -Confirm:$false
|
||||
Move-ActiveMailboxDatabase -Identity "BL-ARC-01" -ActivateOnServer "EXCH-SE-03" -Confirm:$false
|
||||
```
|
||||
|
||||
### Validate the Database Moves
|
||||
Confirm that no active database remains on the target:
|
||||
|
||||
```powershell
|
||||
Get-MailboxDatabaseCopyStatus * |
|
||||
Where-Object { $_.Status -eq "Mounted" -and $_.Name -like "*\$TargetServer" } |
|
||||
Format-Table Name, Status, ActiveDatabaseCopy, CopyQueueLength, ReplayQueueLength -Auto
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```text
|
||||
<no output>
|
||||
```
|
||||
|
||||
Review each move result and confirm that `Status` is `Succeeded`, `NumberOfLogsLost` is `0`, and `MountStatusAtMoveEnd` is `Mounted`. If any move fails or reports log loss, stop the cycle and investigate before entering maintenance mode.
|
||||
|
||||
## Place the Target DAG Member Into Maintenance Mode
|
||||
### Drain External Client Traffic
|
||||
If the environment uses a load balancer, reverse proxy, monitoring probe, backup job, or third-party connector that targets individual Exchange servers, drain or disable the target member according to that platform's documented procedure. Confirm that healthy remaining members are serving the traffic before continuing.
|
||||
|
||||
### Drain Hub Transport
|
||||
From Exchange Management Shell on the administrative shell host, set the target Hub Transport component to draining:
|
||||
|
||||
```powershell
|
||||
Set-ServerComponentState -Identity $TargetServer -Component HubTransport -State Draining -Requester Maintenance
|
||||
```
|
||||
|
||||
Restart the Microsoft Exchange Transport service on the target to initiate queue draining:
|
||||
|
||||
```powershell
|
||||
Invoke-Command -ComputerName $TargetServer -ScriptBlock {
|
||||
Restart-Service MSExchangeTransport
|
||||
}
|
||||
```
|
||||
|
||||
### Run the DAG Maintenance Script
|
||||
From Exchange Management Shell on the administrative shell host, run the Exchange-provided DAG maintenance script:
|
||||
|
||||
```powershell
|
||||
Set-Location $ExScripts
|
||||
|
||||
.\StartDagServerMaintenance.ps1 -ServerName $TargetServer -MoveComment "Rolling Exchange update" -PauseClusterNode
|
||||
```
|
||||
|
||||
The script blocks database activation, pauses the target cluster node, moves any remaining active databases, and moves critical DAG functionality away from the target. The command can take time without producing continuous console output.
|
||||
|
||||
### Redirect Pending Transport Messages
|
||||
Redirect messages still pending on the target to the healthy server selected for the current cycle:
|
||||
|
||||
```powershell
|
||||
Redirect-Message -Server $TargetServer -Target $RedirectTargetFqdn -Confirm:$false
|
||||
```
|
||||
|
||||
### Set the Server-Wide Maintenance State
|
||||
Place the target server into Exchange server-wide maintenance mode:
|
||||
|
||||
```powershell
|
||||
Set-ServerComponentState -Identity $TargetServer -Component ServerWideOffline -State Inactive -Requester Maintenance
|
||||
```
|
||||
|
||||
### Validate Maintenance Mode
|
||||
Check effective Exchange component states:
|
||||
|
||||
```powershell
|
||||
Get-ServerComponentState -Identity $TargetServer |
|
||||
Format-Table Component, State -Auto
|
||||
```
|
||||
|
||||
`ServerWideOffline` must report `Inactive`. In the standard maintenance state, only `Monitoring` and `RecoveryActionsEnabled` should remain `Active`; investigate any other component that remains active before rebooting the server.
|
||||
|
||||
Confirm the database activation policy is blocked:
|
||||
|
||||
```powershell
|
||||
Get-MailboxServer -Identity $TargetServer |
|
||||
Format-List Name, DatabaseCopyAutoActivationPolicy
|
||||
```
|
||||
|
||||
Confirm that the target cluster node is paused:
|
||||
|
||||
```powershell
|
||||
Get-ClusterNode -Name $TargetServer |
|
||||
Format-List Name, State
|
||||
```
|
||||
|
||||
Confirm that no active databases remain on the target:
|
||||
|
||||
```powershell
|
||||
Get-MailboxDatabaseCopyStatus * |
|
||||
Where-Object { $_.Status -eq "Mounted" -and $_.Name -like "*\$TargetServer" } |
|
||||
Format-Table Name, Status, ActiveDatabaseCopy -Auto
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```text
|
||||
<no output>
|
||||
```
|
||||
|
||||
Confirm that the target transport queues have drained:
|
||||
|
||||
```powershell
|
||||
Get-Queue -Server $TargetServer |
|
||||
Sort-Object MessageCount -Descending |
|
||||
Format-Table Identity, Status, MessageCount, NextHopDomain -Auto
|
||||
```
|
||||
|
||||
Confirm that the PAM is hosted by another DAG member:
|
||||
|
||||
```powershell
|
||||
Get-DatabaseAvailabilityGroup -Identity $DagName -Status |
|
||||
Format-List PrimaryActiveManager, OperationalServers
|
||||
```
|
||||
|
||||
!!! warning "Do Not Reboot Until Every Maintenance Gate Passes"
|
||||
Do not install updates or reboot the target until it has no mounted databases, its database activation policy is `Blocked`, its cluster node is `Paused`, `ServerWideOffline` is `Inactive`, its transport queues are drained, and the PAM is hosted by another member.
|
||||
|
||||
## Install Exchange and Windows Updates
|
||||
### Reboot Before Installing the Exchange Update
|
||||
A clean reboot before Exchange Setup or an Exchange SU or HU reduces failures caused by pending file handles and services that do not stop cleanly. From the administrative shell host, reboot the target:
|
||||
|
||||
```powershell
|
||||
Restart-Computer -ComputerName $TargetServer -Force
|
||||
```
|
||||
|
||||
Wait until the server is reachable, then revalidate that maintenance state persisted:
|
||||
|
||||
```powershell
|
||||
Get-ServerComponentState -Identity $TargetServer |
|
||||
Where-Object { $_.Component -eq "ServerWideOffline" } |
|
||||
Format-Table Server, Component, State -Auto
|
||||
|
||||
Get-ClusterNode -Name $TargetServer |
|
||||
Format-List Name, State
|
||||
|
||||
Get-MailboxDatabaseCopyStatus * |
|
||||
Where-Object { $_.Status -eq "Mounted" -and $_.Name -like "*\$TargetServer" }
|
||||
```
|
||||
|
||||
Do not launch the update if `ServerWideOffline` is not `Inactive`, the cluster node is not `Paused`, or a database mounted on the target after reboot.
|
||||
|
||||
### Install an Exchange CU or Build Upgrade
|
||||
Mount the correct Exchange installation media on the target server. From an elevated Command Prompt on the target, run Setup by using the full media path so Windows does not invoke the installed `Setup.exe` from the Exchange binary directory.
|
||||
|
||||
=== "Diagnostic Data Off"
|
||||
|
||||
```cmd
|
||||
D:\Setup.exe /Mode:Upgrade /IAcceptExchangeServerLicenseTerms_DiagnosticDataOFF
|
||||
```
|
||||
|
||||
=== "Diagnostic Data On"
|
||||
|
||||
```cmd
|
||||
D:\Setup.exe /Mode:Upgrade /IAcceptExchangeServerLicenseTerms_DiagnosticDataON
|
||||
```
|
||||
|
||||
Replace `D:` with the actual mounted-media drive. Wait for Exchange Setup to complete successfully and review `C:\ExchangeSetupLogs\ExchangeSetup.log` before continuing.
|
||||
|
||||
!!! danger "A CU Upgrade Is Not Reversible by Uninstalling It"
|
||||
Do not treat a CU as an ordinary removable patch. If the CU fails, keep the server in maintenance mode and repair or recover that server by following Microsoft Exchange recovery guidance.
|
||||
|
||||
### Install an Exchange SU or HU
|
||||
Run the exact update package specified by the applicable release article from an elevated Command Prompt on the target server:
|
||||
|
||||
```cmd
|
||||
C:\ExchangeUpdates\<EXCHANGE_UPDATE_FILENAME>.exe
|
||||
```
|
||||
|
||||
Replace `<EXCHANGE_UPDATE_FILENAME>` with the staged package name. Do not launch the installer from a non-elevated shell or by double-clicking it in File Explorer.
|
||||
|
||||
Do not stop IIS, WMI, Windows Event Log, or other Windows services preemptively unless the release article or a matching Microsoft troubleshooting procedure explicitly instructs you to do so. Wait for the installer to report successful completion before continuing.
|
||||
|
||||
### Install Approved Windows Updates
|
||||
Install the approved Windows Server updates while the target remains in maintenance mode. Apply prerequisites in the order required by the Exchange release notes, and continue rebooting as required until the server has no remaining approved updates or pending restart.
|
||||
|
||||
### Perform the Final Maintenance Reboot
|
||||
After Exchange and Windows updates have completed, reboot the target one final time:
|
||||
|
||||
```powershell
|
||||
Restart-Computer -ComputerName $TargetServer -Force
|
||||
```
|
||||
|
||||
Wait for Windows, Active Directory connectivity, the Cluster service, and Exchange services to initialize before beginning return-to-service validation.
|
||||
|
||||
## Return the Updated DAG Member to Service
|
||||
### Verify the Installed Exchange Build
|
||||
From Exchange Management Shell on the administrative shell host, confirm the base build:
|
||||
|
||||
```powershell
|
||||
Get-ExchangeServer -Identity $TargetServer |
|
||||
Format-Table Name, Edition, AdminDisplayVersion -Auto
|
||||
```
|
||||
|
||||
Query the local `ExSetup.exe` file on the target to identify the installed SU or HU file version:
|
||||
|
||||
```powershell
|
||||
Invoke-Command -ComputerName $TargetServer -ScriptBlock {
|
||||
$VersionInfo = (Get-Item (Join-Path $env:ExchangeInstallPath "bin\ExSetup.exe")).VersionInfo
|
||||
|
||||
[PSCustomObject]@{
|
||||
Server = $env:COMPUTERNAME
|
||||
ProductVersion = $VersionInfo.ProductVersion
|
||||
FileVersion = $VersionInfo.FileVersion
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Compare both values with the intended Microsoft build. An unchanged `AdminDisplayVersion` does not, by itself, prove that an SU or HU failed to install.
|
||||
|
||||
### Validate Exchange Services
|
||||
From the administrative shell host, run:
|
||||
|
||||
```powershell
|
||||
Test-ServiceHealth -Server $TargetServer
|
||||
```
|
||||
|
||||
Inspect Exchange-related services on the target:
|
||||
|
||||
```powershell
|
||||
Invoke-Command -ComputerName $TargetServer -ScriptBlock {
|
||||
Get-CimInstance Win32_Service |
|
||||
Where-Object { $_.Name -like "MSExchange*" -or $_.Name -eq "FMS" } |
|
||||
Select-Object Name, DisplayName, State, StartMode, ExitCode
|
||||
} | Format-Table -Auto
|
||||
```
|
||||
|
||||
Compare the result with `C:\ExchangeUpdates\PreUpdate-Services.csv` from that same server. Do not enable a service merely because it is stopped; protocol services and role-specific services may intentionally be manual or stopped.
|
||||
|
||||
!!! warning "Keep the Server in Maintenance Mode During Repair"
|
||||
If required Exchange services are disabled, fail to start, or report dependency errors, keep the target in maintenance mode. Repair the service state and complete the failed update before running the return-to-service commands.
|
||||
|
||||
### Remove the Server-Wide Maintenance State
|
||||
From Exchange Management Shell on the administrative shell host, restore the server-wide component state:
|
||||
|
||||
```powershell
|
||||
Set-ServerComponentState -Identity $TargetServer -Component ServerWideOffline -State Active -Requester Maintenance
|
||||
```
|
||||
|
||||
### Run the DAG Return-to-Service Script
|
||||
Run the Exchange-provided DAG maintenance exit script:
|
||||
|
||||
```powershell
|
||||
Set-Location $ExScripts
|
||||
|
||||
.\StopDagServerMaintenance.ps1 -ServerName $TargetServer
|
||||
```
|
||||
|
||||
The script resumes the cluster node, restores database activation policy, and resumes database copies hosted by the member.
|
||||
|
||||
### Resume Hub Transport
|
||||
Return Hub Transport to active state:
|
||||
|
||||
```powershell
|
||||
Set-ServerComponentState -Identity $TargetServer -Component HubTransport -State Active -Requester Maintenance
|
||||
```
|
||||
|
||||
Restart transport on the target:
|
||||
|
||||
```powershell
|
||||
Invoke-Command -ComputerName $TargetServer -ScriptBlock {
|
||||
Restart-Service MSExchangeTransport
|
||||
}
|
||||
```
|
||||
|
||||
### Validate the Restored Member
|
||||
Confirm Exchange component states:
|
||||
|
||||
```powershell
|
||||
Get-ServerComponentState -Identity $TargetServer |
|
||||
Format-Table Component, State -Auto
|
||||
```
|
||||
|
||||
Confirm database activation is unrestricted:
|
||||
|
||||
```powershell
|
||||
Get-MailboxServer -Identity $TargetServer |
|
||||
Format-List Name, DatabaseCopyAutoActivationPolicy
|
||||
```
|
||||
|
||||
Confirm the cluster node is up:
|
||||
|
||||
```powershell
|
||||
Get-ClusterNode -Name $TargetServer |
|
||||
Format-List Name, State
|
||||
```
|
||||
|
||||
Run service and replication health:
|
||||
|
||||
```powershell
|
||||
Test-ServiceHealth -Server $TargetServer
|
||||
Test-ReplicationHealth -Identity $TargetServer
|
||||
```
|
||||
|
||||
Inspect transport queues:
|
||||
|
||||
```powershell
|
||||
Get-Queue -Server $TargetServer |
|
||||
Sort-Object MessageCount -Descending |
|
||||
Format-Table Identity, Status, MessageCount, NextHopDomain -Auto
|
||||
```
|
||||
|
||||
Do not restore the target to a load balancer or other external traffic source until required Exchange components are active, required services are running, the cluster node is up, replication health passes, and transport queues are processing normally.
|
||||
|
||||
### Restore External Client Traffic
|
||||
Return the target to its load balancer pool, monitoring platform, backup schedule, mail gateway, and any other system that was intentionally drained. Confirm the external health checks recognize the server as healthy before restoring active mailbox databases to it.
|
||||
|
||||
## Restore the Intended Database Placement
|
||||
### Wait for Database Copies to Catch Up
|
||||
Before moving an active database back to the updated member, confirm every copy is healthy and all queues have drained:
|
||||
|
||||
```powershell
|
||||
Get-MailboxDatabaseCopyStatus * |
|
||||
Sort-Object Name |
|
||||
Format-Table Name, Status, CopyQueueLength, ReplayQueueLength, ContentIndexState -Auto
|
||||
```
|
||||
|
||||
Do not activate a copy on the updated member while it is `Failed`, `Suspended`, `Disconnected`, `ServiceDown`, or building a persistent queue.
|
||||
|
||||
!!! warning "Run Only the Current Cycle's Restore Block"
|
||||
The following restore blocks are cycle-specific. Do not move databases for a different cycle.
|
||||
|
||||
### Cycle 1 Database Restore
|
||||
Move `BL-MBX-03` back to `EXCH-SE-03`:
|
||||
|
||||
```powershell
|
||||
Move-ActiveMailboxDatabase -Identity "BL-MBX-03" -ActivateOnServer "EXCH-SE-03" -Confirm:$false
|
||||
```
|
||||
|
||||
### Cycle 2 Database Restore
|
||||
Move both databases back to `EXCH-SE-02`:
|
||||
|
||||
```powershell
|
||||
Move-ActiveMailboxDatabase -Identity "BL-MBX-02" -ActivateOnServer "EXCH-SE-02" -Confirm:$false
|
||||
Move-ActiveMailboxDatabase -Identity "BL-ARC-02" -ActivateOnServer "EXCH-SE-02" -Confirm:$false
|
||||
```
|
||||
|
||||
### Cycle 3 Database Restore
|
||||
Move both databases back to `EXCH-SE-01`:
|
||||
|
||||
```powershell
|
||||
Move-ActiveMailboxDatabase -Identity "BL-MBX-01" -ActivateOnServer "EXCH-SE-01" -Confirm:$false
|
||||
Move-ActiveMailboxDatabase -Identity "BL-ARC-01" -ActivateOnServer "EXCH-SE-01" -Confirm:$false
|
||||
```
|
||||
|
||||
### Validate the Restored Placement
|
||||
Confirm active database placement:
|
||||
|
||||
```powershell
|
||||
Get-MailboxDatabaseCopyStatus * |
|
||||
Where-Object { $_.Status -eq "Mounted" } |
|
||||
Sort-Object ActiveDatabaseCopy, Name |
|
||||
Format-Table Name, Status, ActiveDatabaseCopy, CopyQueueLength, ReplayQueueLength -Auto
|
||||
```
|
||||
|
||||
Expected Bunny Lab placement:
|
||||
|
||||
```text
|
||||
Name Status ActiveDatabaseCopy CopyQueueLength ReplayQueueLength
|
||||
---- ------ ------------------ --------------- -----------------
|
||||
BL-ARC-01\EXCH-SE-01 Mounted EXCH-SE-01 0 0
|
||||
BL-MBX-01\EXCH-SE-01 Mounted EXCH-SE-01 0 0
|
||||
BL-ARC-02\EXCH-SE-02 Mounted EXCH-SE-02 0 0
|
||||
BL-MBX-02\EXCH-SE-02 Mounted EXCH-SE-02 0 0
|
||||
BL-MBX-03\EXCH-SE-03 Mounted EXCH-SE-03 0 0
|
||||
```
|
||||
|
||||
Temporary queues may appear immediately after activation. Wait until all copy and replay queues return to `0` before declaring the cycle complete.
|
||||
|
||||
## Validate the Completed Cycle
|
||||
Run replication health against every DAG member:
|
||||
|
||||
```powershell
|
||||
$DagMembers | ForEach-Object {
|
||||
Test-ReplicationHealth -Identity $_
|
||||
}
|
||||
```
|
||||
|
||||
Check all database copies:
|
||||
|
||||
```powershell
|
||||
Get-MailboxDatabaseCopyStatus * |
|
||||
Sort-Object Name |
|
||||
Format-Table Name, Status, CopyQueueLength, ReplayQueueLength, ContentIndexState -Auto
|
||||
```
|
||||
|
||||
Run the Exchange Health Checker against the updated member:
|
||||
|
||||
```powershell
|
||||
& "C:\Scripts\HealthChecker.ps1" -Server $TargetServer
|
||||
```
|
||||
|
||||
The cycle is complete only when:
|
||||
|
||||
- The target reports the intended Exchange build
|
||||
- Required Exchange services pass `Test-ServiceHealth`
|
||||
- The target cluster node is `Up`
|
||||
- Database activation policy is `Unrestricted`
|
||||
- Required Exchange components are active
|
||||
- Every applicable replication-health check returns `Passed`
|
||||
- Every active database copy reports `Mounted`
|
||||
- Every passive database copy reports `Healthy`
|
||||
- Every copy and replay queue is `0`
|
||||
- Transport queues are processing normally
|
||||
- The intended active database placement is restored
|
||||
- External health checks and monitoring report the target as healthy
|
||||
- The post-update Health Checker report contains no unresolved update-blocking finding
|
||||
|
||||
Do not begin the next cycle until all conditions are satisfied. Repeat the common workflow with the next cycle's variables and database move block.
|
||||
|
||||
## Final DAG Validation
|
||||
### Confirm a Consistent Exchange Build
|
||||
After all three cycles are complete, confirm the base build recorded for every DAG member:
|
||||
|
||||
```powershell
|
||||
$DagMembers | ForEach-Object {
|
||||
Get-ExchangeServer -Identity $_
|
||||
} | Format-Table Name, Edition, AdminDisplayVersion -Auto
|
||||
```
|
||||
|
||||
Confirm the local `ExSetup.exe` file version on every member:
|
||||
|
||||
```powershell
|
||||
Invoke-Command -ComputerName $DagMembers -ScriptBlock {
|
||||
$VersionInfo = (Get-Item (Join-Path $env:ExchangeInstallPath "bin\ExSetup.exe")).VersionInfo
|
||||
|
||||
[PSCustomObject]@{
|
||||
Server = $env:COMPUTERNAME
|
||||
ProductVersion = $VersionInfo.ProductVersion
|
||||
FileVersion = $VersionInfo.FileVersion
|
||||
}
|
||||
} | Sort-Object Server | Format-Table -Auto
|
||||
```
|
||||
|
||||
All three servers must report the same intended Exchange build unless Microsoft explicitly documents a temporary mixed-build state during the active maintenance window.
|
||||
|
||||
### Validate Database Health and Placement
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
Get-MailboxDatabaseCopyStatus * |
|
||||
Sort-Object Name |
|
||||
Format-Table Name, Status, CopyQueueLength, ReplayQueueLength, ContentIndexState -Auto
|
||||
|
||||
Get-MailboxDatabaseCopyStatus * |
|
||||
Where-Object { $_.Status -eq "Mounted" } |
|
||||
Sort-Object ActiveDatabaseCopy, Name |
|
||||
Format-Table Name, Status, ActiveDatabaseCopy, CopyQueueLength, ReplayQueueLength -Auto
|
||||
```
|
||||
|
||||
Confirm every active copy is `Mounted`, every passive copy is `Healthy`, all queues are `0`, and placement matches the Bunny Lab topology table.
|
||||
|
||||
### Validate Replication, Services, Components, and Cluster State
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
$DagMembers | ForEach-Object {
|
||||
Test-ServiceHealth -Server $_
|
||||
Test-ReplicationHealth -Identity $_
|
||||
}
|
||||
|
||||
Get-ClusterNode |
|
||||
Format-Table Name, State, NodeWeight, DynamicWeight -Auto
|
||||
|
||||
$DagMembers | ForEach-Object {
|
||||
Get-ServerComponentState -Identity $_ |
|
||||
Where-Object { $_.State -ne "Active" } |
|
||||
Select-Object Server, Component, State
|
||||
}
|
||||
```
|
||||
|
||||
Investigate every unexpected non-active component state. Protocol components that are deliberately disabled must match the documented Bunny Lab design rather than being assumed healthy merely because they were disabled before the update.
|
||||
|
||||
### Validate Mail Flow
|
||||
From Exchange Management Shell on each source server, test mail flow to another DAG member. The following examples cover all three members:
|
||||
|
||||
```powershell
|
||||
Test-Mailflow -Identity "EXCH-SE-01" -TargetMailboxServer "EXCH-SE-02"
|
||||
Test-Mailflow -Identity "EXCH-SE-02" -TargetMailboxServer "EXCH-SE-03"
|
||||
Test-Mailflow -Identity "EXCH-SE-03" -TargetMailboxServer "EXCH-SE-01"
|
||||
```
|
||||
|
||||
Each test must return `Success`. Also validate inbound and outbound mail flow through the organization's real mail gateways and confirm the client-access namespaces used by the environment are healthy.
|
||||
|
||||
### Run the Final Health Checker Audit
|
||||
Run the current Exchange Health Checker against every DAG member:
|
||||
|
||||
```powershell
|
||||
$DagMembers | ForEach-Object {
|
||||
& "C:\Scripts\HealthChecker.ps1" -Server $_
|
||||
}
|
||||
```
|
||||
|
||||
Archive the final reports with the change record.
|
||||
|
||||
## Troubleshooting
|
||||
### The Installer Reports Files in Use or Cannot Stop Services
|
||||
Do not select an installer option that ignores locked files, and do not terminate the Windows Event Log service. Exit the installer, confirm the server remains in maintenance mode, reboot it, and rerun the update from an elevated Command Prompt.
|
||||
|
||||
If the problem persists:
|
||||
|
||||
- Confirm the update package matches the installed CU
|
||||
- Confirm the server was rebooted immediately before the installation attempt
|
||||
- Review the selected update's known issues and antivirus exclusion guidance
|
||||
- Preserve `C:\ExchangeSetupLogs`
|
||||
- Use the Microsoft SetupAssist or Setup Log Reviewer tooling appropriate to the failure
|
||||
- Follow the matching procedure in [Fix Failed Exchange Server Updates](https://learn.microsoft.com/en-us/troubleshoot/exchange/client-connectivity/exchange-security-update-issues)
|
||||
|
||||
Do not reuse process IDs from an earlier attempt or another server. Process IDs are transient, and terminating an unidentified Windows or Exchange process can leave the installation in a worse state.
|
||||
|
||||
### Exchange Services Are Disabled After the Update
|
||||
Compare the current service configuration with `C:\ExchangeUpdates\PreUpdate-Services.csv` from the same server:
|
||||
|
||||
```powershell
|
||||
Invoke-Command -ComputerName $TargetServer -ScriptBlock {
|
||||
$Baseline = Import-Csv "C:\ExchangeUpdates\PreUpdate-Services.csv"
|
||||
$Current = Get-CimInstance Win32_Service |
|
||||
Where-Object { $_.Name -like "MSExchange*" -or $_.Name -eq "FMS" }
|
||||
|
||||
foreach ($Before in $Baseline) {
|
||||
$After = $Current | Where-Object { $_.Name -eq $Before.Name }
|
||||
|
||||
if ($After -and ($After.State -ne $Before.State -or $After.StartMode -ne $Before.StartMode)) {
|
||||
[PSCustomObject]@{
|
||||
Name = $Before.Name
|
||||
BeforeState = $Before.State
|
||||
AfterState = $After.State
|
||||
BeforeStartMode = $Before.StartMode
|
||||
AfterStartMode = $After.StartMode
|
||||
}
|
||||
}
|
||||
}
|
||||
} | Format-Table -Auto
|
||||
```
|
||||
|
||||
Restore only a service whose required startup mode is confirmed by the server's baseline, current Exchange role, and Microsoft guidance:
|
||||
|
||||
```powershell
|
||||
Set-Service -Name <SERVICE_NAME> -StartupType Automatic
|
||||
Start-Service -Name <SERVICE_NAME>
|
||||
```
|
||||
|
||||
If the service fails with dependency error `1068`, inspect its required services:
|
||||
|
||||
```powershell
|
||||
Get-Service -Name <SERVICE_NAME> -RequiredServices |
|
||||
Format-Table Name, DisplayName, Status, StartType -Auto
|
||||
```
|
||||
|
||||
Correct the failed dependency before retrying the dependent service. Do not automatically enable IMAP, POP, EdgeSync, or another optional service that was intentionally disabled before the update.
|
||||
|
||||
### An Exchange Component Remains Inactive
|
||||
Inspect the effective and requester-specific component states:
|
||||
|
||||
```powershell
|
||||
Get-ServerComponentState -Identity $TargetServer |
|
||||
Format-Table Component, State -Auto
|
||||
|
||||
Get-ServerComponentState -Identity $TargetServer -Component <COMPONENT_NAME> |
|
||||
Format-List Component, State, LocalStates, RemoteStates
|
||||
```
|
||||
|
||||
Correct the requester that actually holds the component inactive. Do not repeatedly issue `Requester Maintenance` commands when the inactive state belongs to `Functional`, `HealthAPI`, or another requester.
|
||||
|
||||
If a failed Exchange update left `ServerWideOffline`, `Monitoring`, or `RecoveryActionsEnabled` inactive under the `Functional` requester, and the failed installation has already been repaired, restore only the affected states:
|
||||
|
||||
```powershell
|
||||
Set-ServerComponentState -Identity $TargetServer -Component ServerWideOffline -State Active -Requester Functional
|
||||
Set-ServerComponentState -Identity $TargetServer -Component Monitoring -State Active -Requester Functional
|
||||
Set-ServerComponentState -Identity $TargetServer -Component RecoveryActionsEnabled -State Active -Requester Functional
|
||||
```
|
||||
|
||||
Rerun `Test-ServiceHealth` and `Test-ReplicationHealth` after the correction.
|
||||
|
||||
### High Availability Remains Offline After Maintenance Ends
|
||||
If a database move fails with an error stating that the `HighAvailability` component is offline, inspect its requester-specific state:
|
||||
|
||||
```powershell
|
||||
Get-ServerComponentState -Identity $TargetServer -Component HighAvailability |
|
||||
Format-List Component, State, LocalStates, RemoteStates
|
||||
```
|
||||
|
||||
Confirm `StopDagServerMaintenance.ps1` completed successfully, the cluster node is `Up`, database activation is `Unrestricted`, and `MSExchangeRepl` is running. If every requester reports `Active` but Active Manager state remains stale, restart the replication service on the target:
|
||||
|
||||
```powershell
|
||||
Invoke-Command -ComputerName $TargetServer -ScriptBlock {
|
||||
Restart-Service MSExchangeRepl
|
||||
Get-Service MSExchangeRepl
|
||||
}
|
||||
```
|
||||
|
||||
Rerun replication health and retry the database move only after every applicable check passes.
|
||||
|
||||
### Exchange Setup Reports Success but the Build Does Not Change
|
||||
Confirm Setup was launched from the mounted media by using an absolute path such as `D:\Setup.exe` or, from PowerShell in the media root, `.\Setup.exe`. Running only `Setup.exe` can invoke the installed copy under the Exchange binary path instead of the intended installation media.
|
||||
|
||||
Review `C:\ExchangeSetupLogs\ExchangeSetup.log`, correct the launch path, and rerun Setup while the server remains in maintenance mode.
|
||||
|
||||
### Outlook on the Web or the Exchange Admin Center Fails After the Update
|
||||
First confirm the Exchange update completed successfully and required services are running. Review the matching symptom in Microsoft's failed-update guidance rather than running a generic post-update command sequence.
|
||||
|
||||
For an IIS state that specifically requires a service restart, run from an elevated PowerShell session on the affected server:
|
||||
|
||||
```powershell
|
||||
Restart-Service -Name WAS, W3SVC
|
||||
```
|
||||
|
||||
Do not make `UpdateCas.ps1`, `UpdateConfigFiles.ps1`, `IISADMIN`, ADSI changes, or forced process termination part of the normal update workflow. Use a repair command only when an authoritative troubleshooting procedure identifies the same failure condition and explains the required validation.
|
||||
|
||||
## Recovery and Stop Conditions
|
||||
If a target server cannot be returned to service:
|
||||
|
||||
- Keep `ServerWideOffline` inactive and keep the cluster node paused
|
||||
- Keep database activation blocked on the failed member
|
||||
- Leave active databases on healthy DAG members
|
||||
- Do not begin maintenance on another member
|
||||
- Preserve Exchange Setup logs, Windows event logs, Health Checker reports, and the pre-update service baseline
|
||||
- Repair the update or perform the documented Exchange server recovery procedure
|
||||
- Revalidate quorum, database redundancy, transport, client access, and backup status before resuming the rolling upgrade
|
||||
|
||||
The rolling update is complete only after all three members report the intended Exchange build, every required health check passes, the documented database placement is restored, and Bunny Lab mail flow and client access are validated end to end.
|
||||
|
||||
## Reference Documentation
|
||||
- [Manage Database Availability Groups in Exchange Server](https://learn.microsoft.com/en-us/exchange/high-availability/manage-ha/manage-dags)
|
||||
- [Upgrade Exchange to the Latest Cumulative Update](https://learn.microsoft.com/en-us/exchange/plan-and-deploy/install-cumulative-updates)
|
||||
- [Use Unattended Mode in Exchange Setup](https://learn.microsoft.com/en-us/exchange/plan-and-deploy/deploy-new-installations/unattended-installs)
|
||||
- [Exchange Server Build Numbers and Release Dates](https://learn.microsoft.com/en-us/exchange/new-features/build-numbers-and-release-dates)
|
||||
- [Exchange Server Update FAQ](https://learn.microsoft.com/en-us/exchange/plan-and-deploy/post-installation-tasks/security-best-practices/exchange-server-update-faq)
|
||||
- [Fix Failed Exchange Server Updates](https://learn.microsoft.com/en-us/troubleshoot/exchange/client-connectivity/exchange-security-update-issues)
|
||||
- [Exchange Server Health Checker](https://microsoft.github.io/CSS-Exchange/Diagnostics/HealthChecker/)
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
- Microsoft Exchange
|
||||
- Email
|
||||
---
|
||||
|
||||
### Purpose:
|
||||
Sometimes Microsoft Exchange Server will misbehave and the services will need to be *bumped* to fix them. This script iterates over all of the Exchange-related services and restarts them automatically for you.
|
||||
|
||||
``` powershell
|
||||
$servicelist = Get-Service | Where-Object {$_.DisplayName -like "Microsoft Exchange *"}
|
||||
$servicelist += Get-Service | Where-Object {$_.DisplayName -eq "IIS Admin Service"}
|
||||
$servicelist += Get-Service | Where-Object { $_.DisplayName –eq "Windows Management Instrumentation" }
|
||||
$servicelist += Get-Service | Where-Object { $_.DisplayName –eq "World Wide Web Publishing Service" }
|
||||
|
||||
foreach($service in $servicelist){
|
||||
Set-Service $service -StartupType Automatic
|
||||
Start-Service $service
|
||||
}
|
||||
```
|
||||
+17
-11
@@ -10,8 +10,9 @@ Sometimes things go awry with backup servers and Hyper-V and a bunch of extra `.
|
||||
This script automatically iterates through the entire differencing disk chain all the way back to the base disk / parent, and automatically collapses the chain downward from the newest checkpoint (provided as an argument to the script) to the original (non-differencing) base disk. This can automate a huge amount of work when this issue happens due to backup servers or other unexplainable anomalies.
|
||||
|
||||
## Powershell Script
|
||||
You need to copy the contents of the following somewhere on your computer and save it as `Get-HyperVParentDisks.ps1`.
|
||||
``` powershell
|
||||
You need to copy the contents of the following somewhere on your computer and save it as `Get-HyperVParentDisks.ps1`.
|
||||
|
||||
```powershell
|
||||
param (
|
||||
[Parameter(Mandatory=$true, HelpMessage="Specify the path to the AVHDX file.")]
|
||||
[string]$AVHDXPath,
|
||||
@@ -36,11 +37,11 @@ function Get-AllParentDisksChain {
|
||||
$parentDiskChain = @()
|
||||
while ($CurrentDisk) {
|
||||
$parentDisk = Get-ParentDisk -ChildDisk $CurrentDisk
|
||||
if ($parentDisk) {
|
||||
if ($parentDisk) {
|
||||
$parentDiskChain += $CurrentDisk # Add the current disk to the chain before moving to the parent
|
||||
$CurrentDisk = $parentDisk
|
||||
} else {
|
||||
break
|
||||
$CurrentDisk = $parentDisk
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
$parentDiskChain += $CurrentDisk # Add the base disk at the end of the chain
|
||||
@@ -49,7 +50,7 @@ function Get-AllParentDisksChain {
|
||||
|
||||
function Merge-DiskIntoParent {
|
||||
param ([string]$ChildDisk, [string]$ParentDisk, [int]$DiskNumber, [int]$TotalDisks)
|
||||
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Output "[Differential Disk $DiskNumber of $TotalDisks]"
|
||||
Write-Output "Child: $ChildDisk"
|
||||
@@ -100,7 +101,8 @@ Merge Disks:
|
||||
`.\Get-HyperVParentDisks.ps1 -MergeIntoParents -AVHDXPath "Z:\Example\Virtual Hard Disks\Example.avhdx"`
|
||||
|
||||
!!! info "Example Output"
|
||||
```
|
||||
|
||||
```text
|
||||
Starting parent disk search for: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_E5F78673-3DAD-4211-AC0A-A3BDEB763B63.avhdx
|
||||
Total parent disks found: 6
|
||||
|
||||
@@ -136,7 +138,8 @@ Dry Run (Non-Destructive):
|
||||
`.\Get-HyperVParentDisks.ps1 -MergeIntoParents -DryRun -AVHDXPath "Z:\Example\Virtual Hard Disks\Example.avhdx"`
|
||||
|
||||
!!! info "Example Output"
|
||||
```
|
||||
|
||||
```text
|
||||
Starting parent disk search for: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_E5F78673-3DAD-4211-AC0A-A3BDEB763B63.avhdx
|
||||
Total parent disks found: 6
|
||||
|
||||
@@ -145,7 +148,7 @@ Dry Run (Non-Destructive):
|
||||
Child: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_E5F78673-3DAD-4211-AC0A-A3BDEB763B63.avhdx
|
||||
Parent: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_8B9EDF27-6B7D-4766-AE60-ED67BF3055AE.avhdx
|
||||
[Dry Run] Would merge child into parent
|
||||
|
||||
|
||||
[Differential Disk 2 of 6]
|
||||
Child: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_8B9EDF27-6B7D-4766-AE60-ED67BF3055AE.avhdx
|
||||
Parent: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_6607B03C-E3F8-49CC-A69B-68BA3DACE81F.avhdx
|
||||
@@ -166,4 +169,7 @@ Dry Run (Non-Destructive):
|
||||
Parent: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER.vhdx
|
||||
[Dry Run] Would merge child into parent
|
||||
Merge process completed.
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Virtualization and Storage Documentation](<../../../reference/Virtualization and Storage/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+5
-2
@@ -4,7 +4,7 @@ tags:
|
||||
- Scripting
|
||||
---
|
||||
|
||||
**Purpose**:
|
||||
## Purpose
|
||||
You may find that you cannot delete a VHDX file for a virtual machine you removed from Hyper-V and/or Hyper-V Failover Cluster, and either cannot afford to, or do not want to reboot your virtualization host(s) to unlock the file locked by `SYSTEM`.
|
||||
|
||||
Run the following commands to unlock the file and delete it:
|
||||
@@ -12,4 +12,7 @@ Run the following commands to unlock the file and delete it:
|
||||
```powershell
|
||||
Dismount-VHD -Path "C:\Path\To\Disk.vhdx" -ErrorAction SilentlyContinue
|
||||
Remove-Item -Path "C:\Path\To\Disk.vhdx" -Force
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Virtualization and Storage Documentation](<../../../reference/Virtualization and Storage/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+7
-3
@@ -4,11 +4,12 @@ tags:
|
||||
- Scripting
|
||||
---
|
||||
|
||||
**Purpose**: Sometimes a Hyper-V Failover Cluster node does not want to shut down, or is having issues preventing you from migrating VMs to another node in the cluster, etc. In these situations, you can run this script to force a cluster node to reboot itself.
|
||||
## Purpose
|
||||
Sometimes a Hyper-V Failover Cluster node does not want to shut down, or is having issues preventing you from migrating VMs to another node in the cluster, etc. In these situations, you can run this script to force a cluster node to reboot itself.
|
||||
|
||||
!!! warning "Run from a Different Server"
|
||||
You absolutely do not want to run the script locally on the node that is having the issues. There are commands that can only take place if the script is ran on another node in the cluster (or another domain-joined device) logged-in with a domain administrator account.
|
||||
|
||||
|
||||
```powershell
|
||||
# PowerShell Script to Reboot a Hyper-V Failover Cluster Node and Kill clussvc
|
||||
|
||||
@@ -62,4 +63,7 @@ Invoke-Command -ComputerName $hostName -ScriptBlock {
|
||||
|
||||
# Output the completion
|
||||
Write-Host "Reboot for $hostName should now be underway."
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Virtualization and Storage Documentation](<../../../../reference/Virtualization and Storage/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+8
-5
@@ -4,10 +4,10 @@ tags:
|
||||
- Scripting
|
||||
---
|
||||
|
||||
**Purpose**:
|
||||
This script *bumps* any replication that has entered a paused state due to a replication error. The script will record failed attempts at restarting the replication. The logs will rotate out every 5-days.
|
||||
## Purpose
|
||||
This script *bumps* any replication that has entered a paused state due to a replication error. The script will record failed attempts at restarting the replication. The logs will rotate out every 5-days.
|
||||
|
||||
``` powershell
|
||||
```powershell
|
||||
# Define the directory to store the log files
|
||||
$logDir = "C:\ClusterStorage\Volume1\Scripts\Logs"
|
||||
if (-not (Test-Path $logDir)) {
|
||||
@@ -30,7 +30,7 @@ if (-not (Test-Path $logFile)) {
|
||||
}
|
||||
|
||||
# Delete log files older than 5 days
|
||||
Get-ChildItem -Path $logDir -Filter "ReplicationLog_*.txt" | Where-Object {
|
||||
Get-ChildItem -Path $logDir -Filter "ReplicationLog_*.txt" | Where-Object {
|
||||
$_.CreationTime -lt (Get-Date).AddDays(-5)
|
||||
} | Remove-Item
|
||||
|
||||
@@ -72,4 +72,7 @@ foreach ($node in $clusterNodes) {
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Virtualization and Storage Documentation](<../../../../reference/Virtualization and Storage/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
tags:
|
||||
- RAID
|
||||
- Bash
|
||||
- Scripting
|
||||
- Linux
|
||||
---
|
||||
|
||||
## Purpose
|
||||
Keep the recorded mdadm grow command and array-progress check together. Confirm the current array layout and intended RAID conversion before adapting the `/dev/md0` example.
|
||||
|
||||
https://www.digitalocean.com/community/tutorials/how-to-create-raid-arrays-with-mdadm-on-ubuntu-16-04
|
||||
|
||||
```sh
|
||||
sudo mdadm --grow /dev/md0 -l 5
|
||||
cat /proc/mdstat
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Virtualization and Storage Documentation](<../../../reference/Virtualization and Storage/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+7
-5
@@ -10,7 +10,6 @@ tags:
|
||||
This script is ran via cronjob on `cluster-node-02` at midnight to rollback the deeplab environment automatically to a previous snapshot nightly.
|
||||
|
||||
### Bash Script
|
||||
|
||||
```sh title="/root/deeplab-rollback.sh"
|
||||
#!/usr/bin/env bash
|
||||
# ProxmoxVE Nightly DeepLab Rollback Script
|
||||
@@ -58,12 +57,15 @@ Type `crontab -e` to add an entry to run the job at midnight every day.
|
||||
|
||||
=== "With Logging"
|
||||
|
||||
``` sh
|
||||
```sh
|
||||
0 0 * * * /root/deeplab-rollback.sh >> /var/log/deeplab-rollback.log 2>&1
|
||||
```
|
||||
|
||||
|
||||
=== "Without Logging"
|
||||
|
||||
``` sh
|
||||
```sh
|
||||
0 0 * * * /root/deeplab-rollback.sh 2>&1
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Proxmox Documentation](<../../../reference/Virtualization and Storage/Proxmox/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
tags:
|
||||
- Virtualization and Storage
|
||||
- Scripts
|
||||
- Documentation
|
||||
---
|
||||
|
||||
# Virtualization and Storage
|
||||
## Purpose
|
||||
Find scripts for virtualization and storage. Follow the subject guide to choose the relevant environment and connect this material to the other document types.
|
||||
|
||||
## Includes
|
||||
- Hyper-V
|
||||
- Linux
|
||||
- Proxmox
|
||||
|
||||
## Follow the Subject
|
||||
[Virtualization and Storage](<../../reference/Virtualization and Storage/index.md>) explains the relationships and offers starting points for the documented tasks.
|
||||
+6
-2
@@ -19,8 +19,9 @@ This script is designed to iterate over every computer device within an Active D
|
||||
|
||||
### Script
|
||||
You can find the full script below, save it as `UserProfileDataPruner.ps1`:
|
||||
|
||||
```powershell
|
||||
<#
|
||||
<#
|
||||
UserProfileDataPruner.ps1
|
||||
Prune stale local user profile data on Windows workstations.
|
||||
|
||||
@@ -328,4 +329,7 @@ process {
|
||||
}
|
||||
|
||||
end { }
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Windows and Linux Documentation](<../../../reference/Windows and Linux/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+5
-2
@@ -8,7 +8,7 @@ tags:
|
||||
## Purpose
|
||||
Sometimes when you try to run Windows Updates, you may run into issues where updates just fail to install for seemingly nebulous reasons. You can run the following commands (in order) to try to resolve the issue.
|
||||
|
||||
!!! info "Run Commands from (CMD) Commandline, not powershell.
|
||||
!!! info "Run Commands from (CMD) Commandline, not powershell."
|
||||
|
||||
```powershell
|
||||
# Imaging integrity Rrepair tools
|
||||
@@ -36,4 +36,7 @@ net start usosvc
|
||||
```
|
||||
|
||||
!!! info "Attempt Windows Updates"
|
||||
At this point, you can try re-running Windows Updates and seeing if the device makes it past the errors and installs the updates successfully or not. If not, **panic**.
|
||||
At this point, you can try re-running Windows Updates and seeing if the device makes it past the errors and installs the updates successfully or not. If not, **panic**.
|
||||
|
||||
## Related Documentation
|
||||
- [Related Windows and Linux Documentation](<../../../reference/Windows and Linux/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+6
-3
@@ -4,8 +4,8 @@ tags:
|
||||
- Scripting
|
||||
---
|
||||
|
||||
**Purpose**:
|
||||
Sometimes you need to restart a service across every computer in an Active Directory Domain. This powershell script will restart a specific service by name domain-wide. Each device will be processed in a serialized nature, one-by-one.
|
||||
## Purpose
|
||||
Start the `cagservice` RMM agent service on the domain servers selected by the script. The current implementation starts that service; it does not reboot computers or restart every service.
|
||||
|
||||
!!! warning "Under Connstruction"
|
||||
This document is under construction and not generalized for general purpose use yet. Manual work needs to be done to repurpose this script for general usage.
|
||||
@@ -37,4 +37,7 @@ foreach ($server in $servers) {
|
||||
}
|
||||
|
||||
Write-Host "Script execution completed." -ForegroundColor Green
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Windows and Linux Documentation](<../../../reference/Windows and Linux/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
+8
-5
@@ -6,7 +6,7 @@ tags:
|
||||
- Scripting
|
||||
---
|
||||
|
||||
**Purpose**:
|
||||
## Purpose
|
||||
You may need to upgrade a device to Windows 11 using an ISO stored on a UNC Network Share, the script below handles that.
|
||||
|
||||
!!! note "Environment Variables"
|
||||
@@ -23,7 +23,7 @@ You may need to upgrade a device to Windows 11 using an ISO stored on a UNC Netw
|
||||
```powershell
|
||||
function generateSHA256 ($executable, $storedHash) {
|
||||
$fileBytes = [io.File]::ReadAllBytes("$executable")
|
||||
$bytes = [Security.Cryptography.HashAlgorithm]::Create("SHA256").ComputeHash($fileBytes)
|
||||
$bytes = [Security.Cryptography.HashAlgorithm]::Create("SHA256").ComputeHash($fileBytes)
|
||||
$varCalculatedHash=-Join ($bytes | ForEach {"{0:x2}" -f $_})
|
||||
if ($storedHash -match $varCalculatedHash) {
|
||||
write-host "+ Filehash verified for file $executable`: $storedHash"
|
||||
@@ -239,7 +239,7 @@ if (($env:usrImagePath -as [string]).Length -lt 2 -or $env:usrImagePath -eq 'Sup
|
||||
write-host `r
|
||||
write-host " Generate a Windows 11 ISO download link good for 24 hours at:"
|
||||
write-host " https://www.microsoft.com/software-download/windows11"
|
||||
exit 1
|
||||
exit 1
|
||||
} elseif ($env:usrImagePath -match 'software-download.microsoft.com') {
|
||||
#microsoft
|
||||
write-host ": ISO Download location: Microsoft servers."
|
||||
@@ -372,7 +372,7 @@ switch -Regex ($varTPM -as [string]) {
|
||||
} '1$' {
|
||||
write-host "+ TPM installed and active."
|
||||
} $null {
|
||||
write-host "! ERROR: A fault has occurred during the TPM checking subroutine. Please report this."
|
||||
write-host "! ERROR: A fault has occurred during the TPM checking subroutine. Please report this."
|
||||
quitOr
|
||||
}
|
||||
|
||||
@@ -516,4 +516,7 @@ if ($env:usrReboot -match 'true') {
|
||||
write-host " Please allow ~4 hours for the setup preparation step to conclude and then reboot the"
|
||||
write-host " device to begin the upgrade process."
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
- [Related Windows and Linux Documentation](<../../../reference/Windows and Linux/index.md>) — Find the connected deployments, procedures, and references for this subject.
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
tags:
|
||||
- Windows and Linux
|
||||
- Scripts
|
||||
- Documentation
|
||||
---
|
||||
|
||||
# Windows and Linux
|
||||
## Purpose
|
||||
Find scripts for windows and linux. Follow the subject guide to choose the relevant environment and connect this material to the other document types.
|
||||
|
||||
## Includes
|
||||
- Windows
|
||||
|
||||
## Follow the Subject
|
||||
[Windows and Linux](<../../reference/Windows and Linux/index.md>) explains the relationships and offers starting points for the documented tasks.
|
||||
@@ -1,36 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
- Robocopy
|
||||
- Batch
|
||||
- Scripting
|
||||
- Windows
|
||||
---
|
||||
|
||||
Robocopy is a useful tool that can be leveraged to copy files and folders from one location to another (e.g. Over the network to another server) without losing file and folder ACLs (permissions / ownership data).
|
||||
|
||||
!!! warning "Run as Domain Admin"
|
||||
When you run Robocopy, especially when transferring data across the network to another remote server, you need to be sure to run the command prompt under the session of a domain admin. Secondly, it needs to be ran as an administrator to ensure the command is successful. This can be done by going to the start menu and typing "**Command Prompt**" > **Right Clicking** > "**Run as Administrator**" while logged in as a domain administrator.
|
||||
|
||||
An example of using Robocopy is below, with a full breakdown:
|
||||
```powershell
|
||||
robocopy "E:\Source" "Z:\Destination" /Z /B /R:5 /W:5 /MT:4 /COPYALL /E
|
||||
```
|
||||
|
||||
- `robocopy "Source" "Destination"` : Initiates the Robocopy command to copy files from the specified source directory to the designated destination directory.
|
||||
- `/Z` : Enables Robocopy's restartable mode, which allows it to resume file transfer from the point of interruption once the network connection is re-established.
|
||||
- `/B` : Activates Backup Mode, enabling Robocopy to override Access Control Lists (ACLs) and copy files regardless of the existing file or folder permissions.
|
||||
- `R:5` : Sets the maximum retry count to 5, meaning Robocopy will attempt to copy a file up to five times if the initial attempt fails.
|
||||
- `W:5` : Configures a wait time of 5 seconds between retry attempts, providing a brief pause before trying to copy a file again.
|
||||
- `/MT:4` : Employs multi-threading with 4 threads, allowing Robocopy to process multiple files simultaneously, each in its own thread.
|
||||
- `/COPYALL` : Instructs Robocopy to preserve all file and folder attributes, including security permissions, timestamps, and ownership information during the copy process.
|
||||
- `/E` : Directs Robocopy to include all subdirectories in the copy operation, ensuring even empty directories are replicated in the destination.
|
||||
|
||||
!!! tip "Usage of Administrative Shares"
|
||||
Whenever dealing with copying data from one server to another, try to leverage "Administrative Shares", also referred to as "Default Shares". These exist in such a way that, if the server exists in a Windows-based domain, you can type something like `\\SERVER\C$` or `\\SERVER\E$` to access files and bypass most file access restrictions (ACLs). This generally only applies to read-access, write-access may be denied in some circumstances.
|
||||
|
||||
An adjusted example can be seen below to account for this usage.
|
||||
**This example assumes you are running robocopy from the destination computer**.
|
||||
**Remember**: You are always **PULLING** data with administrative shares, not pushing it, the source should be the administrative share, and the destination should be local (in this example). There are scenarios where you can move data between two network shares, but its best (and cleaner) to always have a remote/local relationship in the transfer.
|
||||
```powershell
|
||||
robocopy "\\SERVER\E$\SOURCE" "E:\DESTINATION" /Z /B /R:5 /W:5 /MT:4 /COPYALL /E
|
||||
```
|
||||
+16
-22
@@ -1,34 +1,28 @@
|
||||
---
|
||||
tags:
|
||||
- Scripts
|
||||
- Index
|
||||
- Documentation
|
||||
---
|
||||
|
||||
# Scripts
|
||||
## Purpose
|
||||
Quick-use scripts and snippets for day-to-day operations.
|
||||
Find reusable utilities by the system or task they manage. Each page retains its language-specific code and operational context.
|
||||
|
||||
## Includes
|
||||
- Bash, PowerShell, and Batch snippets
|
||||
- One-off utilities and helpers
|
||||
- Applications
|
||||
- Automation
|
||||
- Identity and Certificates
|
||||
- Networking and Access
|
||||
- Virtualization and Storage
|
||||
- Windows and Linux
|
||||
|
||||
## New Document Template
|
||||
````markdown
|
||||
# <Script Title>
|
||||
## Purpose
|
||||
<why this script exists>
|
||||
## Start with a Subject
|
||||
- [Applications](<../reference/Applications/index.md>) — Find applications by the service they provide, then continue to their deployment, authentication, data, and maintenance documentation.
|
||||
- [Automation](<../reference/Automation/index.md>) — Connect source control, automation controllers, managed hosts, and configuration delivery. Use the documented execution environment and authentication method for each workflow.
|
||||
- [Identity and Certificates](<../reference/Identity and Certificates/index.md>) — Connect directory services, certificate trust, single sign-on, and application authentication. Start with the identity system involved, then follow the integration or maintenance procedure.
|
||||
- [Networking and Access](<../reference/Networking and Access/index.md>) — Find the DNS, proxy, VPN, and remote-access instructions that connect users and services. Use the address plans to identify the intended network before changing connectivity.
|
||||
- [Virtualization and Storage](<../reference/Virtualization and Storage/index.md>) — Follow the relationship between hypervisors, shared storage, guest disks, and recovery procedures. Select the documented storage design before choosing a maintenance command.
|
||||
- [Windows and Linux](<../reference/Windows and Linux/index.md>) — Find workstation and server operating-system setup, updates, and repairs. Storage, networking, and identity tasks are linked to their subject guides when they cross operating-system boundaries.
|
||||
|
||||
## Script
|
||||
```sh
|
||||
# Script content
|
||||
```
|
||||
|
||||
## Usage
|
||||
```sh
|
||||
# Example usage
|
||||
```
|
||||
|
||||
## Notes
|
||||
- <edge cases or caveats>
|
||||
````
|
||||
## Find Related Knowledge
|
||||
[The subject guides](<../reference/index.md>) connect these scripts to the other document roles.
|
||||
|
||||
Reference in New Issue
Block a user