משהו בדקה עם AI
@echo off
rem ============================================================
rem גיבוי עץ תיקיות - לחיצה כפולה על קובץ זה מפעילה את הסקריפט
rem ואז בוחרים תיקיית מקור ותיקיית יעד בחלון גרפי.
rem ============================================================
chcp 65001 >nul
title גיבוי עץ תיקיות
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0Backup-Tree.ps1"
echo.
pause
לשמור לקובץ באט
ואת זה לקובץ Backup-Tree.PS1
<#
=====================================================================================
גיבוי עץ תיקיות (Backup-Tree)
מה הסקריפט עושה:
- מקבל תיקיית מקור (לדוגמה C:\Users) ומעתיק רק את מבנה התיקיות שלה
(כל התיקיות ותתי-התיקיות) לתיקיית יעד - בלי שום קובץ.
- בכל תיקיה ביעד כותב קובץ טקסט בשם _file_list.txt שמפרט בטבלה
את כל הקבצים שהיו בתיקיה המקורית: שם קובץ, גודל, תאריך שינוי,
תאריך יצירה, ועוד - כולל שמות תיקיות המשנה.
- בשורש היעד כותב גם קובץ _OVERVIEW.txt עם סיכום העץ כולו,
וקובץ _ERRORS.txt אם היו תיקיות שלא ניתן היה לקרוא.
התוצאה: "מפה" מלאה של התיקיה במשקל של כמה מגה-בייטים במקרה הטוב.
איך מריצים:
1) לחיצה כפולה על Backup-Tree.bat - ואז בוחרים תיקיית מקור ותיקיית יעד.
2) או משורת הפקודה:
powershell -ExecutionPolicy Bypass -File Backup-Tree.ps1 -SourcePath "C:\Users" -DestinationPath "D:\Backup"
רכיבים נוספים (לא חובה):
-IncludeHidden : כברירת מחדל קבצים ותיקיות נסתרות נכללים. השתמשו ב-NoHidden כדי להשמיט אותם.
-NoGui : בלי חלון בחירת תיקיות (מועיל כשמריצים משורת הפקודה).
=====================================================================================
#>
param(
[string]$SourcePath,
[string]$DestinationPath,
[switch]$NoHidden,
[switch]$NoGui
)
# ---------------------------------------------------------------
# הגדרות כלליות
# ---------------------------------------------------------------
$ErrorActionPreference = 'Stop'
# תמיכה בעברית בחלון המסוף
try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch { }
# בורר תיקיות גרפי (לא חובה)
function Select-FolderDialog {
param([string]$Description, [string]$InitialDir)
try {
Add-Type -AssemblyName System.Windows.Forms -ErrorAction Stop | Out-Null
} catch {
return Read-Host "$Description (הקלד נתיב)"
}
if (-not ("System.Windows.Forms.FolderBrowserDialog" -as [type])) {
return Read-Host "$Description (הקלד נתיב)"
}
$dialog = New-Object System.Windows.Forms.FolderBrowserDialog
$dialog.Description = $Description
$dialog.ShowNewFolderButton = $true
if ($InitialDir) {
try { $dialog.SelectedPath = $InitialDir } catch { }
}
if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
return $dialog.SelectedPath
}
return $null
}
# פורמט גודל קריא: 1.5 MB, 230 KB וכו'
function Format-Size {
param([long]$Bytes)
if ($Bytes -ge 1GB) { return ("{0:N2} GB" -f ($Bytes / 1GB)) }
if ($Bytes -ge 1MB) { return ("{0:N2} MB" -f ($Bytes / 1MB)) }
if ($Bytes -ge 1KB) { return ("{0:N2} KB" -f ($Bytes / 1KB)) }
return ("{0} B" -f $Bytes)
}
# ---------------------------------------------------------------
# איסוף פרמטרים (עם חלונות בחירה למי שמריץ בלחיצה כפולה)
# ---------------------------------------------------------------
if (-not $SourcePath) {
if (-not $NoGui) {
$SourcePath = Select-FolderDialog -Description "בחר את תיקיית המקור (תחילת העץ)" -InitialDir $env:USERPROFILE
}
if (-not $SourcePath) {
$SourcePath = Read-Host "הקלד נתיב לתיקיית המקור"
}
if (-not $SourcePath) { Write-Host "בוטל - לא נבחר מקור." -ForegroundColor Yellow; exit 0 }
}
if (-not (Test-Path -LiteralPath $SourcePath -PathType Container)) {
Write-Host "שגיאה: הנתיב לא קיים או אינו תיקיה:`n $SourcePath" -ForegroundColor Red
exit 1
}
$SourcePath = (Resolve-Path -LiteralPath $SourcePath).Path.TrimEnd('\')
if (-not $DestinationPath) {
if (-not $NoGui) {
$DestinationPath = Select-FolderDialog -Description "בחר לאן ליצור את עץ הגיבוי (מומלץ: מחוץ לתיקיית המקור)" -InitialDir $env:USERPROFILE
}
if (-not $DestinationPath) {
$srcLeaf = Split-Path $SourcePath -Leaf
if (-not $srcLeaf) { $srcLeaf = "Root" }
$DestinationPath = Join-Path (Split-Path $SourcePath -Parent) ("TreeBackup_" + $srcLeaf + "_" + (Get-Date -Format 'yyyyMMdd_HHmm'))
}
}
$DestinationPath = [System.IO.Path]::GetFullPath($DestinationPath).TrimEnd('\')
if ($DestinationPath -eq $SourcePath) {
Write-Host "שגיאה: תיקיית היעד זהה לתיקיית המקור. בחר מיקום אחר." -ForegroundColor Red
exit 1
}
if ($DestinationPath.StartsWith($SourcePath + '\')) {
Write-Host "שגיאה: תיקיית היעד נמצאת בתוך תיקיית המקור. בחר מיקום אחר (לדוגמה D:\ או שולחן העבודה)." -ForegroundColor Red
exit 1
}
Write-Host ""
Write-Host "מקור : $SourcePath" -ForegroundColor Cyan
Write-Host "יעד : $DestinationPath" -ForegroundColor Cyan
Write-Host ""
# ---------------------------------------------------------------
# עיקר העבודה
# ---------------------------------------------------------------
$force = if ($NoHidden) { $false } else { $true }
$script:DirCount = 0
$script:FileCount = 0
$script:TotalSize = 0L
$script:ErrorLines = [System.Collections.Generic.List[string]]::new()
$script:OverviewLines = [System.Collections.Generic.List[string]]::new()
$overviewTitle = "סיכום עץ התיקיות - מבנה בלבד (ללא תוכן הקבצים)`r`n" +
"מקור : $SourcePath`r`n" +
"נוצר : $(Get-Date -Format 'dd/MM/yyyy HH:mm')`r`n" +
("=" * 70)
# פונקציה רקורסיבית: יוצרת תיקיה מקבילה ביעד + קובץ רשימה.
# IsLink - עבור תיקיות קישור (Junction/Symlink): יוצרים תיקיה + רשימה קצרה, בלי לרדת לעומק
function Copy-TreeStructure {
param(
[System.IO.DirectoryInfo]$CurrentDir,
[string]$OutDir,
[string]$RelPath, # נתיב יחסי מהמקור, ריק עבור התיקיה הראשית
[switch]$IsLink
)
# 1) יוצרים את התיקיה המקבילה ביעד
New-Item -ItemType Directory -Path $OutDir -Force | Out-Null
$script:DirCount++
# תיקיית קישור: רשימה קצרה בלבד
if ($IsLink) {
$content = [System.Collections.Generic.List[string]]::new()
$content.Add("רשימת תוכן התיקיה")
$content.Add("==================")
$content.Add("")
$content.Add("תיקיה מלאה : $($CurrentDir.FullName)")
$content.Add("נסרק בתאריך: $(Get-Date -Format 'dd/MM/yyyy HH:mm')")
$content.Add("הערה : תיקיית קישור (Junction/Symlink) - התוכן לא נסרק כדי למנוע לולאות")
$content.Add("")
$content.Add("(תוכנה כקישור - לפרטים מלאים צריך לסרוק את היעד אליו היא מצביעה)")
$txtPath = Join-Path $OutDir "_file_list.txt"
try {
[System.IO.File]::WriteAllLines($txtPath, $content, [System.Text.UTF8Encoding]::new($true))
} catch {
$script:ErrorLines.Add("לא ניתן לכתוב את קובץ הרשימה ב- $($CurrentDir.FullName) : $($_.Exception.Message)")
}
Write-Host ("[{0}] {1} (קישור)" -f $script:DirCount, $RelPath) -ForegroundColor DarkGray
return
}
# 2) אוספים את הקבצים והתיקיות הישירים בתיקיה הזו
$files = Get-ChildItem -LiteralPath $CurrentDir.FullName -File -Force:$force -ErrorAction SilentlyContinue
$subs = Get-ChildItem -LiteralPath $CurrentDir.FullName -Directory -Force:$force -ErrorAction SilentlyContinue
$totalSizeHere = 0L
foreach ($f in $files) { $totalSizeHere += $f.Length }
# 3) בונים את תוכן קובץ הרשימה
$content = [System.Collections.Generic.List[string]]::new()
$content.Add("רשימת תוכן התיקיה")
$content.Add("==================")
$content.Add("")
$content.Add("תיקיה מלאה : $($CurrentDir.FullName)")
$content.Add("נסרק בתאריך: $(Get-Date -Format 'dd/MM/yyyy HH:mm')")
$content.Add("מספר קבצים : $($files.Count)")
$content.Add("גודל כולל : $(Format-Size $totalSizeHere)")
$content.Add("תיקיות משנה: $($subs.Count)")
$content.Add("")
$content.Add("-- קבצים בתיקיה ($($files.Count)) --")
$content.Add("")
if ($files.Count -eq 0) {
$content.Add(" (התיקיה ריקה מקבצים)")
} else {
$rows = $files |
Sort-Object Name |
Select-Object @{N='שם קובץ'; E={$_.Name}},
@{N='גודל'; E={Format-Size ([long]$_.Length)}},
@{N='שונה לאחרונה'; E={$_.LastWriteTime.ToString('dd/MM/yyyy HH:mm')}},
@{N='נוצר'; E={$_.CreationTime.ToString('dd/MM/yyyy HH:mm')}},
@{N='נסתר'; E={if ($_.Attributes -band [System.IO.FileAttributes]::Hidden) { 'כן' } else { '' }}}
$table = $rows | Format-Table -AutoSize | Out-String -Width 320
$content.Add($table.TrimEnd("`r", "`n", ' '))
}
$content.Add("")
$content.Add("-- תיקיות משנה ($($subs.Count)) --")
$content.Add("")
if ($subs.Count -eq 0) {
$content.Add(" (אין תיקיות משנה)")
} else {
foreach ($s in ($subs | Sort-Object Name)) {
$content.Add(" [DIR] $($s.Name)")
}
}
$txtPath = Join-Path $OutDir "_file_list.txt"
try {
[System.IO.File]::WriteAllLines($txtPath, $content, [System.Text.UTF8Encoding]::new($true))
} catch {
$script:ErrorLines.Add("לא ניתן לכתוב את קובץ הרשימה ב- $($CurrentDir.FullName) : $($_.Exception.Message)")
}
# 4) עדכון סטטיסטיקות כלליות
$script:FileCount += $files.Count
$script:TotalSize += $totalSizeHere
$rel = if ($RelPath) { $RelPath } else { "<שורש>" }
$script:OverviewLines.Add(("{0,-45} | {1,7} קבצים | {2,12}" -f $rel, $files.Count, (Format-Size $totalSizeHere)))
Write-Host ("[{0}] {1}" -f $script:DirCount, $RelPath) -ForegroundColor Gray
# 5) רקורסיה לתיקיות משנה (לא נכנסים לתיקיות קישור כדי להימנע מלולאות)
foreach ($s in $subs) {
$isLink = ($s.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0
if ($isLink) {
Copy-TreeStructure -CurrentDir $s -OutDir (Join-Path $OutDir $s.Name) -RelPath "$RelPath\$($s.Name)" -IsLink
continue
}
Copy-TreeStructure -CurrentDir $s -OutDir (Join-Path $OutDir $s.Name) -RelPath "$RelPath\$($s.Name)"
}
}
# יצירת תיקיית היעד עצמה
New-Item -ItemType Directory -Path $DestinationPath -Force | Out-Null
$rootInfo = Get-Item -LiteralPath $SourcePath
Copy-TreeStructure -CurrentDir $rootInfo -OutDir $DestinationPath -RelPath ""
# ---------------------------------------------------------------
# כתיבת קבצי הסיכום בשורש היעד
# ---------------------------------------------------------------
$overview = [System.Collections.Generic.List[string]]::new()
$overview.Add($overviewTitle)
$overview.Add("")
$overview.Add("מספר תיקיות כולל : $script:DirCount")
$overview.Add("מספר קבצים כולל : $script:FileCount")
$overview.Add("גודל כולל של כל הקבצים: $(Format-Size $script:TotalSize)")
$overview.Add("")
$overview.Add("-- כל התיקיות בעץ (קבצים ישירים בכל תיקיה) --")
$overview.Add("")
foreach ($line in $script:OverviewLines) { $overview.Add($line) }
$overviewPath = Join-Path $DestinationPath "_OVERVIEW.txt"
[System.IO.File]::WriteAllLines($overviewPath, $overview, [System.Text.UTF8Encoding]::new($true))
if ($script:ErrorLines.Count -gt 0) {
$err = [System.Collections.Generic.List[string]]::new()
$err.Add("תיקיות שלא ניתן היה לקרוא (לרוב: חסרות הרשאות):")
$err.Add("=" * 60)
foreach ($e in $script:ErrorLines) { $err.Add($e) }
$errPath = Join-Path $DestinationPath "_ERRORS.txt"
[System.IO.File]::WriteAllLines($errPath, $err, [System.Text.UTF8Encoding]::new($true))
}
# ---------------------------------------------------------------
# סיכום למשתמש
# ---------------------------------------------------------------
Write-Host ""
Write-Host "הסתיים!" -ForegroundColor Green
Write-Host ("נסרקו {0} תיקיות ו-{1} קבצים (סך הכל {2})." -f $script:DirCount, $script:FileCount, (Format-Size $script:TotalSize))
Write-Host "עץ הגיבוי נוצר כאן: $DestinationPath" -ForegroundColor Cyan
Write-Host " - בכל תיקיה: _file_list.txt (רשימת הקבצים שלה)"
Write-Host " - בשורש: _OVERVIEW.txt (סיכום כל העץ)"
if ($script:ErrorLines.Count -gt 0) {
Write-Host " - שים לב: _ERRORS.txt (תיקיות שלא ניתן היה לקרוא)" -ForegroundColor Yellow
}
Write-Host ""
Write-Host "טיפ: לשליחת הרשימה לחבר - דחסו את תיקיית היעד ל-ZIP ושלחו." -ForegroundColor Green