هل سئمت من رؤية رسائل الخطأ الحمراء المزعجة في نصوص PowerShell الخاصة بك؟ رغم أنها قد تبدو مخيفة، فإن التعامل الصحيح مع الأخطاء أمر أساسي لبناء أتمتة PowerShell موثوقة. في هذا الدليل، ستتعلم كيفية تنفيذ معالجة أخطاء قوية في نصوصك – من فهم أنواع الأخطاء إلى إتقان كتل try/catch.
المتطلبات الأساسية
يفترض هذا الدليل أنك تمتلك:
- Windows PowerShell 5.1 أو PowerShell 7+ مثبتاً
- معرفة أساسية ببرمجة PowerShell
- استعداداً لتقبل الأخطاء كفرص للتعلم!
فهم أنواع أخطاء PowerShell
قبل الغوص في التعامل مع الأخطاء، تحتاج إلى فهم نوعين رئيسيين من الأخطاء التي يمكن أن يرميها PowerShell:
الأخطاء المنتهية
هذه هي الأخطاء الجادة – الأخطاء التي توقف تنفيذ النص تمامًا. ستواجه الأخطاء المنتهية عندما:
- يحتوي نصك على أخطاء في الصياغة تمنع تحليله
- تحدث استثناءات غير معالجة في استدعاءات طرق .NET
- تحدد صراحة
ErrorAction Stop
- تجعل الأخطاء الحرجة أثناء التشغيل من المستحيل الاستمرار
الأخطاء غير المنتهية
هذه أخطاء تشغيلية أكثر شيوعًا لن توقف نصك:
- أخطاء ملف غير موجود
- حالات رفض الإذن
- مشكلات الاتصال بالشبكة
- قيم المعلمات غير صالحة
معامل ErrorAction: خط الدفاع الأول لديك
لنبدأ بمثال عملي. إليك سيناريو يحاول إزالة الملفات الأقدم من عدد معين من الأيام:
param ( [Parameter(Mandatory)] [string]$FolderPath, [Parameter(Mandatory)] [int]$DaysOld ) $Now = Get-Date $LastWrite = $Now.AddDays(-$DaysOld) $oldFiles = (Get-ChildItem -Path $FolderPath -File -Recurse).Where{$_.LastWriteTime -le $LastWrite} foreach ($file in $oldFiles) { Remove-Item -Path $file.FullName Write-Verbose -Message "Successfully removed [$($file.FullName)]." }
بشكل افتراضي، تُولِّد Remove-Item
أخطاءً غير مُنهية. لجعلها تُولِّد أخطاءً منتهية يمكن القبض عليها، أضف -ErrorAction Stop
:
Remove-Item -Path $file.FullName -ErrorAction Stop
مُكَعَّفات Try/Catch: سكينة سويسرية لمعالجة الأخطاء
الآن لنضع عملية إزالة الملفات ضمن مُكَعَّف Try/Catch:
foreach ($file in $oldFiles) { try { Remove-Item -Path $file.FullName -ErrorAction Stop Write-Verbose -Message "Successfully removed [$($file.FullName)]." } catch { Write-Warning "Failed to remove file: $($file.FullName)" Write-Warning "Error: $($_.Exception.Message)" } }
المُكَعَّف “try” يحتوي على الكود الذي قد يُولِّد خطأ. إذا حدث خطأ، ينتقل التنفيذ إلى المُكَعَّف “catch” (إذا كان الخطأ منتهيًا) حيث يمكنك:
- تسجيل الخطأ
- اتخاذ إجراء تصحيحي
- إخطار المسؤولين
- مواصلة تنفيذ السكربت بسلاسة
العمل مع $Error: أداة تحقيق الأخطاء الخاصة بك
يحتفظ PowerShell بمصفوفة من كائنات الخطأ في المتغير التلقائي $Error
. اعتبر $Error
كـ “جهاز تسجيل البيانات الأسود” في PowerShell – فهو يتتبع كل خطأ يحدث خلال جلسة PowerShell الخاصة بك، مما يجعله لا غنى عنه لعمليات الاستكشاف والتصحيح.
ها هي الحالات التي قد ترغب فيها في استخدام $Error
والسبب:
-
حل المشاكل السابقة: حتى لو فاتك رؤية رسالة الخطأ الحمراء،
$Error
يحتفظ بسجل:# عرض تفاصيل الخطأ الأخيرة $Error[0] | Format-List * -Force # النظر في أخطاء الـ 5 الأخيرة $Error[0..4] | Select-Object CategoryInfo, Exception # البحث عن أنواع معينة من الأخطاء $Error | Where-Object { $_.Exception -is [System.UnauthorizedAccessException] }
-
تصحيح النصوص: استخدم
$Error
لفهم ما حدث بشكل خاطئ وأين:# احصل على رقم السطر الدقيق والنص البرمجي حيث حدث الخطأ $Error[0].InvocationInfo | اختيار-كائن ScriptName, ScriptLineNumber, Line # انظر إلى سلسلة الاستدعاء الكاملة للخطأ $Error[0].Exception.StackTrace
-
استرداد الأخطاء والإبلاغ عنها: مثالي لإنشاء تقارير أخطاء مفصلة:
# إنشاء تقرير خطأ function Write-ErrorReport { param($ErrorRecord = $Error[0])
[PSCustomObject]@{ TimeStamp = Get-Date ErrorMessage = $ErrorRecord.Exception.Message ErrorType = $ErrorRecord.Exception.GetType().Name Command = $ErrorRecord.InvocationInfo.MyCommand ScriptLine = $ErrorRecord.InvocationInfo.Line ErrorLineNumber = $ErrorRecord.InvocationInfo.ScriptLineNumber StackTrace = $ErrorRecord.ScriptStackTrace }
}
-
إدارة الجلسة: تنظيف الأخطاء أو التحقق من حالة الخطأ:
# مسح سجل الأخطاء (مفيد في بداية النصوص) $Error.Clear() # عدد الأخطاء الإجمالي (جيد لفحص عتبة الأخطاء) if ($Error.Count -gt 10) { Write-Warning "تم اكتشاف عدد كبير من الأخطاء: $($Error.Count) errors" }
مثال واقعي يجمع بين هذه المفاهيم:
function Test-DatabaseConnections { $Error.Clear() # Start fresh try { # Attempt database operations... } catch { # If something fails, analyze recent errors $dbErrors = $Error | Where-Object { $_.Exception.Message -like "*SQL*" -or $_.Exception.Message -like "*connection*" } if ($dbErrors) { Write-ErrorReport $dbErrors[0] | Export-Csv -Path "C:\\Logs\\DatabaseErrors.csv" -Append } } }
نصائح احترافية:
$Error
يتم الحفاظ عليه لكل جلسة PowerShell- لديه سعة افتراضية تبلغ 256 خطأ (تتحكم بها
$MaximumErrorCount
) - إنها مصفوفة ثابتة الحجم – الأخطاء الجديدة تدفع القديمة للخارج عند الامتلاء
- تحقق دائمًا من
$Error[0]
أولاً – إنه أحدث خطأ - اعتبر مسح
$Error
في بداية السكربتات المهمة لتتبع الأخطاء بشكل نظيف
كتل Catch متعددة: معالجة الأخطاء المستهدفة
تمامًا كما أنك لن تستخدم نفس الأداة لكل وظيفة إصلاح في المنزل، يجب ألا تتعامل مع كل خطأ في PowerShell بنفس الطريقة. تتيح لك كتل catch المتعددة الاستجابة بشكل مختلف لأنواع مختلفة من الأخطاء.
إليك كيف يعمل:
try { Remove-Item -Path $file.FullName -ErrorAction Stop } catch [System.UnauthorizedAccessException] { # This catches permission-related errors Write-Warning "Access denied to file: $($file.FullName)" Request-ElevatedPermissions -Path $file.FullName # Custom function } catch [System.IO.IOException] { # This catches file-in-use errors Write-Warning "File in use: $($file.FullName)" Add-ToRetryQueue -Path $file.FullName # Custom function } catch [System.Management.Automation.ItemNotFoundException] { # This catches file-not-found errors Write-Warning "File not found: $($file.FullName)" Update-FileInventory -RemovePath $file.FullName # Custom function } catch { # This catches any other errors Write-Warning "Unexpected error: $_" Write-EventLog -LogName Application -Source "MyScript" -EntryType Error -EventId 1001 -Message $_ }
أنواع الأخطاء الشائعة التي ستواجهها:
[System.UnauthorizedAccessException]
– تم رفض الإذن[System.IO.IOException]
– الملف مؤمن/قيد الاستخدام[System.Management.Automation.ItemNotFoundException]
– الملف/المسار غير موجود[System.ArgumentException]
– وسائط غير صالحة[System.Net.WebException]
– مشاكل الشبكة/الويب
إليك مثال واقعي يضع ذلك موضع التنفيذ:
function Remove-StaleFiles { [CmdletBinding()] param( [string]$Path, [int]$RetryCount = 3, [int]$RetryDelaySeconds = 30 ) $retryQueue = @() foreach ($file in (Get-ChildItem -Path $Path -File)) { $attempt = 0 do { $attempt++ try { Remove-Item -Path $file.FullName -ErrorAction Stop Write-Verbose "Successfully removed $($file.FullName)" break # Exit the retry loop on success } catch [System.UnauthorizedAccessException] { if ($attempt -eq $RetryCount) { # Log to event log and notify admin $message = "Permission denied after $RetryCount attempts: $($file.FullName)" Write-EventLog -LogName Application -Source "FileCleanup" -EntryType Error -EventId 1001 -Message $message Send-AdminNotification -Message $message # Custom function } else { # Request elevated permissions and retry Request-ElevatedAccess -Path $file.FullName # Custom function Start-Sleep -Seconds $RetryDelaySeconds } } catch [System.IO.IOException] { if ($attempt -eq $RetryCount) { # Add to retry queue for later $retryQueue += $file.FullName Write-Warning "File locked, added to retry queue: $($file.FullName)" } else { # Wait and retry Write-Verbose "File in use, attempt $attempt of $RetryCount" Start-Sleep -Seconds $RetryDelaySeconds } } catch { # Unexpected error - log and move on $message = "Unexpected error with $($file.FullName): $_" Write-EventLog -LogName Application -Source "FileCleanup" -EntryType Error -EventId 1002 -Message $message break # Exit retry loop for unexpected errors } } while ($attempt -lt $RetryCount) } # Return retry queue for further processing if ($retryQueue) { return $retryQueue } }
نصائح احترافية لكتل Catch متعددة:
- ترتيب الأمور مهم – ضع الاستثناءات الأكثر تحديدًا أولاً
- استخدم الوظائف المخصصة لمعالجة كل نوع من أنواع الأخطاء بشكل متسق
- افكر في منطق إعادة المحاولة للأخطاء العابرة
- سجل أنواع الأخطاء المختلفة في مواقع مختلفة
- استخدم نوع الاستثناء الأكثر تحديدًا إذا أمكن
- اختبر كل كتلة catch عن طريق التسبب بكل نوع من أنواع الأخطاء بشكل متعمد
استخدم كتل Finally: قم بتنظيف بيئة العمل
تعتبر كتلة finally فريق التنظيف الخاص بك – حيث تُنفَّذ دائمًا، سواء كان هناك خطأ أم لا. وهذا يجعلها مثالية لـ:
- إغلاق مقابض الملفات
- الفصل عن قواعد البيانات
- إطلاق موارد النظام
- استعادة الإعدادات الأصلية
إليك مثالًا عمليًا:
try { $stream = [System.IO.File]::OpenRead($file.FullName) # Process file contents here... } catch { Write-Warning "Error processing file: $_" } finally { # This runs even if an error occurred if ($stream) { $stream.Dispose() Write-Verbose "File handle released" } }
فكر في كتلة finally مثل قاعدة المعسكر الرشيق: “نظف دائمًا مكان التخييم قبل المغادرة، بغض النظر عما حدث خلال الرحلة.”
أفضل ممارسات معالجة الأخطاء
-
كن محددًا في إجراءات الخطأ
بدلاً من استخدامErrorAction Stop
على نطاق واسع، استخدمه بشكل انتقائي على الأوامر التي تحتاج إلى التقاط الأخطاء. -
استخدم متغيرات الخطأ
Remove-Item $path -ErrorVariable removeError if ($removeError) { Write-Warning "فشلت عملية إزالة العنصر: $($removeError[0].Exception.Message)" }
-
سجل الأخطاء بشكل مناسب
- استخدم Write-Warning للأخطاء القابلة للاسترداد
- استخدم Write-Error للمشكلات الخطيرة
- اعتبر الكتابة إلى سجل أحداث Windows للفشل الحرج
-
تنظيف الموارد
استخدم دائمًا كتل finally لتنظيف الموارد مثل مقبضات الملفات واتصالات الشبكة. -
اختبار معالجة الأخطاء
استفز الأخطاء عمدًا للتحقق من أن معالجة الأخطاء تعمل كما هو متوقع.
جمع كل ذلك معًا
إليك مثال كامل يجمع بين هذه الممارسات الجيدة:
function Remove-OldFiles { [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$FolderPath, [Parameter(Mandatory)] [int]$DaysOld, [string]$LogPath = "C:\\Logs\\file-cleanup.log" ) try { # Validate input if (-not (Test-Path -Path $FolderPath)) { throw "Folder path '$FolderPath' does not exist" } $Now = Get-Date $LastWrite = $Now.AddDays(-$DaysOld) # Find old files $oldFiles = Get-ChildItem -Path $FolderPath -File -Recurse | Where-Object {$_.LastWriteTime -le $LastWrite} foreach ($file in $oldFiles) { try { Remove-Item -Path $file.FullName -ErrorAction Stop Write-Verbose -Message "Successfully removed [$($file.FullName)]" # Log success "$(Get-Date) - Removed file: $($file.FullName)" | Add-Content -Path $LogPath } catch [System.UnauthorizedAccessException] { Write-Warning "Access denied to file: $($file.FullName)" "$ErrorActionPreference - Access denied: $($file.FullName)" | Add-Content -Path $LogPath } catch [System.IO.IOException] { Write-Warning "File in use: $($file.FullName)" "$(Get-Date) - File in use: $($file.FullName)" | Add-Content -Path $LogPath } catch { Write-Warning "Unexpected error removing file: $_" "$(Get-Date) - Error: $_ - File: $($file.FullName)" | Add-Content -Path $LogPath } } } catch { Write-Error "Critical error in Remove-OldFiles: $_" "$(Get-Date) - Critical Error: $_" | Add-Content -Path $LogPath throw # Re-throw error to calling script } }
تتحقق هذه التنفيذ:
- التحقق من معلمات الإدخال
- استخدام كتل catch محددة للأخطاء الشائعة
- تسجيل كل من النجاحات والإخفاقات
- توفير مخرجات مفصلة لاستكشاف الأخطاء وإصلاحها
- إعادة رمي الأخطاء الحرجة إلى البرنامج النصي المستدعي
الخاتمة
يعتبر التعامل السليم مع الأخطاء أمرًا حيويًا لبرمجيات PowerShell الموثوقة. من خلال فهم أنواع الأخطاء واستخدام كتل try/catch بفعالية، يمكنك بناء سكربتات تتعامل مع الفشل بشكل سلس وتقدم ملاحظات ذات معنى. تذكر أن تختبر تعاملات الأخطاء الخاصة بك بدقة – ستحمد نفسك في المستقبل عندما تضطر لحل المشكلات في بيئة الإنتاج!
الآن انطلق والتقط تلك الأخطاء! فقط تذكر – الخطأ السيء الوحيد هو الخطأ غير المعالج.
Source:
https://adamtheautomator.com/powershell-error-handling/