دليل سريع لإتقان التعامل مع أخطاء PowerShell

هل سئمت من رؤية رسائل الخطأ الحمراء المزعجة في نصوص 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 والسبب:

  1. حل المشاكل السابقة: حتى لو فاتك رؤية رسالة الخطأ الحمراء، $Error يحتفظ بسجل:

    # عرض تفاصيل الخطأ الأخيرة
    $Error[0] | Format-List * -Force
    
    # النظر في أخطاء الـ 5 الأخيرة
    $Error[0..4] | Select-Object CategoryInfo, Exception
    
    # البحث عن أنواع معينة من الأخطاء
    $Error | Where-Object { $_.Exception -is [System.UnauthorizedAccessException] }
    
  2. تصحيح النصوص: استخدم $Error لفهم ما حدث بشكل خاطئ وأين:

    # احصل على رقم السطر الدقيق والنص البرمجي حيث حدث الخطأ
    $Error[0].InvocationInfo | اختيار-كائن ScriptName, ScriptLineNumber, Line
    
    # انظر إلى سلسلة الاستدعاء الكاملة للخطأ
    $Error[0].Exception.StackTrace
    
  3. استرداد الأخطاء والإبلاغ عنها: مثالي لإنشاء تقارير أخطاء مفصلة:

    # إنشاء تقرير خطأ
    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
    }
    

    }

  4. إدارة الجلسة: تنظيف الأخطاء أو التحقق من حالة الخطأ:

    # مسح سجل الأخطاء (مفيد في بداية النصوص)
    $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 متعددة:

  1. ترتيب الأمور مهم – ضع الاستثناءات الأكثر تحديدًا أولاً
  2. استخدم الوظائف المخصصة لمعالجة كل نوع من أنواع الأخطاء بشكل متسق
  3. افكر في منطق إعادة المحاولة للأخطاء العابرة
  4. سجل أنواع الأخطاء المختلفة في مواقع مختلفة
  5. استخدم نوع الاستثناء الأكثر تحديدًا إذا أمكن
  6. اختبر كل كتلة 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 مثل قاعدة المعسكر الرشيق: “نظف دائمًا مكان التخييم قبل المغادرة، بغض النظر عما حدث خلال الرحلة.”

أفضل ممارسات معالجة الأخطاء

  1. كن محددًا في إجراءات الخطأ
    بدلاً من استخدام ErrorAction Stop على نطاق واسع، استخدمه بشكل انتقائي على الأوامر التي تحتاج إلى التقاط الأخطاء.

  2. استخدم متغيرات الخطأ

    Remove-Item $path -ErrorVariable removeError
    if ($removeError) {
        Write-Warning "فشلت عملية إزالة العنصر: $($removeError[0].Exception.Message)"
    }
    
  3. سجل الأخطاء بشكل مناسب

    • استخدم Write-Warning للأخطاء القابلة للاسترداد
    • استخدم Write-Error للمشكلات الخطيرة
    • اعتبر الكتابة إلى سجل أحداث Windows للفشل الحرج
  4. تنظيف الموارد
    استخدم دائمًا كتل finally لتنظيف الموارد مثل مقبضات الملفات واتصالات الشبكة.

  5. اختبار معالجة الأخطاء
    استفز الأخطاء عمدًا للتحقق من أن معالجة الأخطاء تعمل كما هو متوقع.

جمع كل ذلك معًا

إليك مثال كامل يجمع بين هذه الممارسات الجيدة:

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/