معالجة الأخطاء وتصميم إعادة التنفيذ في PowerShell ── من فخّ عدم نجاعة try/catch حتى القاعدة الثابتة لـ exit code وإعادة المحاولة
· آخر تحديث: · غو كومورا · PowerShell, Windows, معالجة الأخطاء, إعادة المحاولة, الأتمتة, تحسين التشغيل, سكربت, Task Scheduler
سجل التعديلات (2 تحديثات، آخر تحديث 2 Sep، 2026)
سجل بالتغييرات التي أُجريت على هذا المقال. وحيثما حُفظت نسخة سابقة، تبقى متاحة للقراءة عبر رابط دائم يحمل معرّف DOI.
- أُعيدَت الترجمة العربية كترجمة كاملة عن النص الياباني الأصلي، وأُضيفَت خريطة المعرفة.
- أعيدت الترجمة كترجمة كاملة عن النص الياباني الأصلي. كانت النسخة العربية السابقة مختصراً يسقط أبواباً وجداول ورسوم Mermaid وتعليقات الأشكال وFAQ. أُعيدت هذه العناصر وفق الأصل الياباني، والادّعاءات التقنية مطابقة للنسخة اليابانية.
- النشر الأول
الاستشهاد بهذا المقال(DOI: 10.5281/zenodo.21621781)
هذا المقال محفوظ على Zenodo. يرد أدناه معرّف DOI الذي يشير دائمًا إلى أحدث نسخة، ومعرّف DOI المثبَّت على النسخة التي تقرؤها.
غو كومورا (2026). معالجة الأخطاء وتصميم إعادة التنفيذ في PowerShell ── من فخّ عدم نجاعة try/catch حتى القاعدة الثابتة لـ exit code وإعادة المحاولة. شركة كومورا سوفت ذ.م.م.. https://doi.org/10.5281/zenodo.21621781 https://comcomponent.com/ar/blog/powershell-error-handling-retry-design/
- DOI (أحدث نسخة)
- 10.5281/zenodo.21621781
- DOI (هذه النسخة)
- 10.5281/zenodo.22241075
«فشلت الدفعة الليليّة، لكنّ Task Scheduler أظهرها نجحت (0x0) فلم ينتبه أحد» أو «كتبتُ try/catch لكنّه لا يدخل إلى catch» أو «ينهار مرّة واحدة في الشهر بسبب انقطاع الشبكة اللحظيّ» ── متى ما وُضع سكربت PowerShell في التشغيل، تأتي هذه الأنواع من الاستفسارات حتماً. بين السكربت الذي يُرضيك تشغيله يدوياً على جهازك، والسكربت الذي يعمل كلّ ليلة دون إشراف بشريّ، يقف حاجز اسمه تصميم معالجة الأخطاء وإعادة التنفيذ (إعادة المحاولة).
المزعج أنّ نموذج أخطاء PowerShell يختلف قليلاً عن نموذج الاستثناءات في لغات البرمجة العامّة. «الخطأ ظاهر لكنّ المعالجة تستمرّ» و«يفترض أنّه التُقط في catch لكنّه تسرّب» في معظم الحالات سلوك مطابق لمواصفات PowerShell لا عطل، فإن كُتب دون معرفة الآليّة تُنتَج كمّيّة من «السكربتات التي تكتم الفشل وتكتمل طبيعياً».
يستهدف هذا المقال مسؤولي نظم المعلومات والمطوّرين الذين يؤتمتون المعالجة الروتينيّة داخل الشركة بـ PowerShell، ويرتّب من التمييز بين الخطأ المنهي وغير المنهي، حتى الحكم على نجاح الأوامر الأصليّة، وتصميم exit code الذي يمكن به الحكم على النجاح من جدولة المهام والمراقبة، ونمط إعادة المحاولة الذي يتحمّل الأخطاء المؤقّتة، مع سند من الوثائق الرسميّة.
1. الخلاصة أوّلاً
- تنقسم أخطاء PowerShell إلى «خطأ غير منهِي» و«خطأ منهِي (إنهاء عبارة / إنهاء سكربت)». الخطأ غير المنهي يعرض رسالة ويواصل خطّ الأنابيب، ولا يدخل افتراضياً إلى try/catch**.1
- طريقتا العدّ اثنتان فنرتّبهما أوّلاً. تصنيف الوثائق الرسميّة ثلاث فئات «غير منهِي / إنهاء عبارة / إنهاء سكربت»، وهذا محور «إلى أين يوقف المحرّك» (خطّ الأنابيب فقط / تلك العبارة فقط / مكدّس الاستدعاء بأكمله).1 من ناحية أخرى، ما يريد الكاتب معرفته أوّلاً هو «هل يدخل إلى try/catch»، وعلى هذا المحور يصير النوعان خطأ غير منهِي وخطأ منهِي. يستخدم هذا المقال محور النوعين أساساً، ويلمس تفصيل الفئات الثلاث حيث يلزم.
- القاعدة الثابتة إضافة
-ErrorAction Stopإلى الأمر المراد التقاطه في try/catch. Stop يرفع الخطأ غير المنهي إلى خطأ منهِي فيصير قابلاً للتعامل في catch. توجد أيضاً طريقة جعل$ErrorActionPreference(الافتراض Continue) Stop في بداية السكربت.12 -ErrorActionيكتب فوق$ErrorActionPreferenceلذلك الأمر الواحد. غير أنّ الاثنين ليسا متناظرين تماماً، فما يستطيع-ErrorActionالتحكّم فيه هو الأخطاء غير المنهِية فقط.1- فشل الأوامر الأصليّة (robocopy وgit وEXE خارجيّ) لا يصير خطأ PowerShell افتراضياً. رمز الخروج غير الصفريّ يجعل
$?$falseويدخل$LASTEXITCODE، لكن لا يُنشَأ ErrorRecord ولا يدخل إلى catch. يُحكَم على النجاح بـ$LASTEXITCODE.1 - في PowerShell 7.4 صارت
$PSNativeCommandUseErrorActionPreferenceميّزة رسميّة. عند$trueيُصدِر رمز الخروج غير الصفريّ خطأ غير منهِي، وبالجمع مع$ErrorActionPreference = 'Stop'يمكن التقاطه في try/catch (الافتراض$false).32 - في داخل catch يكون
$_هو ErrorRecord. يمكن تتبّع متن الاستثناء بـ$_.Exception، وفي الخطأ المرفوع معلومات الخطأ الأصليّة بـ$_.Exception.ErrorRecord. بكتلة catch تعيّن نوع الاستثناء يمكن معالجة الأخطاء المتوقَّعة فقط فردياً.14 - بلِّغ النجاح حتماً إلى الخارج بـ exit code. عيِّن رمز الخروج بالكلمة المفتاحيّة
exit، وعند التشغيل بـpwsh -File/powershell.exe -Fileتصير تلك القيمة رمز خروج العمليّة. بلا exit الاكتمال الطبيعيّ 0 والاستثناء غير المعالَج 1.56 - إعادة المحاولة ثلاث مبادئ: قصرها على الأخطاء المؤقّتة، وحدّ أعلى، وidempotency (عدم تغيّر النتيجة عند تكرار التنفيذ نفسه. التفصيل في الفصل 6). لا تموّه أخطاء الأعمال بإعادة المحاولة، ووسِّع الفاصل بتراجع أُسّيّ، وصمِّم بحيث لا تحدث معالجة مزدوجة حتى عند إعادة التنفيذ. باجتماع هذه الثلاث يصير أوّل مرّة «سكربتاً يجوز إعادة تنفيذه».
في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 18، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle
2. نوعان من الأخطاء ── لماذا لا ينجح try/catch
تنقسم أخطاء PowerShell أوّلاً إلى نوعين: خطأ غير منهِي وخطأ منهِي. ثمّ ينقسم الخطأ المنهي إلى خطأ إنهاء عبارة وخطأ إنهاء سكربت، فإن عُدَّ بدقّة صارت ثلاث فئات. الخطأ غير المنهي يبلّغ فقط دون إيقاف خطّ الأنابيب، وخطأ إنهاء العبارة يوقف تلك العبارة فقط ويمضي إلى التالية، وخطأ إنهاء السكربت يرجع مكدّس الاستدعاء بأكمله.1
ما يصير فخّاً في الميدان هو الخطأ غير المنهي. ما تصدره أوامر مثل Get-Content وGet-ChildItem عند فشل معالجة إدخال فرديّ في الغالب خطأ غير منهِي، وتُعرَض رسالة خطأ حمراء لكنّ المعالجة تستمرّ، ولا تدخل إلى try/catch ولا إلى trap.1
# 【فخّ】لا يدخل catch أبداً، ويُعرَض حتى «اكتمل»
try {
Get-Content -Path 'C:\Data\ملف-غير-موجود.txt' # خطأ غير منهِي
Write-Host 'اكتمل' # يُنفَّذ رغم ذلك
}
catch {
Write-Host 'لا يصل إلى هنا'
}
# 【قاعدة ثابتة】ارفع إلى خطأ منهِي بـ -ErrorAction Stop ليُعالَج في catch
try {
Get-Content -Path 'C:\Data\ملف-غير-موجود.txt' -ErrorAction Stop
Write-Host 'اكتمل' # لا يُنفَّذ عند الخطأ
}
catch {
Write-Host "أُمسِك: $($_.Exception.Message)"
}
عندما يعمل -ErrorAction Stop أو $ErrorActionPreference = 'Stop'، يلفّ المحرّك الخطأ غير المنهي بـ ActionPreferenceStopException ويرفعه إلى خطأ منهِي. الآليّة الدقيقة أنّ هذا الخطأ المرفوع يصل إلى catch داخل كتلة try.1
من ناحية أخرى، توجد أيضاً أخطاء تصير منهِية من البداية (= تدخل إلى catch بلا فعل). ذلك خطأ إنهاء العبارة، وتذكر الوثائق الرسميّة مصادر الحدوث التالية.1
- عند استدعاء أمر غير موجود (
CommandNotFoundException) - عند فشل ربط المعاملات (
ParameterBindingException. تمرير سلسلة لا يمكن تحويلها إلى معامل رقميّ، مثلاً) - عندما يرمي تابع .NET استثناء (
[int]::Parse('abc')مثلاً) - عندما يبلّغ أمر أو دالة متقدّمة بـ
$PSCmdlet.ThrowTerminatingError()أنّ «هذا الاستدعاء لم يعد يمكن مواصلته»
كما يدلّ الاسم يوقف «تلك العبارة فقط»، فيستمرّ تنفيذ السكربت من العبارة التالية.1
# خطأ إنهاء عبارة: هذه العبارة تتوقّف، لكن التالية تُنفَّذ
[int]::Parse('abc')
Write-Output 'هذا السطر يُنفَّذ'
# خطأ منهِي، فيدخل catch بلا -ErrorAction Stop
try { [int]::Parse('abc') }
catch { Write-Warning "أُمسِك: $($_.Exception.Message)" }
المزعج أنّ الوضع نفسه مثل «الملفّ غير موجود» قد يكون خطأ غير منهِي أو خطأ إنهاء عبارة حسب تنفيذ جانب الأمر. تمييز الكاتب في كلّ مرّة غير واقعيّ، فالجواب العمليّ توضيح -ErrorAction Stop في السطر المراد التقاطه، وتوحيد الشكل بحيث يصل إلى catch حتماً أيّاً كان.
فكرة «إذن يكفي دائماً $ErrorActionPreference = 'Stop'» نصف صحيحة. في سكربت التنفيذ غير المراقب، التوقّف والإبلاغ بالفشل أأمن من المضيّ مع كتم الخطأ، لذا جعل Stop في البداية افتراض جيّد. غير أنّ $ErrorActionPreference يؤثّر في ذلك النطاق والنطاقات الفرعية، فيتغيّر حتى سلوك الوحدات والدوال المستدعاة، وأنّ معالجة التنظيف التي يجوز فشلها (حذف ملفّ مؤقّت مثلاً) تحتاج إعادة -ErrorAction SilentlyContinue فردياً، فَعِ ذلك.2
3. ماذا تقرأ داخل catch ── مسار ErrorRecord
في $_ داخل كتلة catch يدخل ErrorRecord. المعلومات التي ينبغي إبقاؤها في السجلّ تُؤخَذ من هنا.14
try {
Copy-Item -Path $src -Destination $dest -ErrorAction Stop
}
catch [System.IO.IOException] {
# catch بتعيين النوع يعالج «الفشل المتوقَّع» فقط على حدة.
# حتى الخطأ المرفوع، يطابق المحرّك نوع الاستثناء الأصليّ
Write-Warning "خطأ I/O: $($_.Exception.Message)"
}
catch {
# غير المتوقَّع يُسجَّل بسياقه كاملاً ويُعاد رميه (لا يُكتم)
$rec = $_ # $_ هو ErrorRecord
Write-Warning ('النوع: {0} / الموضع: {1} / الهدف: {2}' -f `
$rec.Exception.GetType().FullName,
$rec.InvocationInfo.PositionMessage,
$rec.TargetObject)
throw # throw بلا وسيط ينشر الخطأ نفسه إلى الأعلى
}
finally {
# finally تُنفَّذ عند النجاح والخطأ وحتى عند الإيقاف بـ Ctrl+C. التنظيف هنا
if ($tempFile -and (Test-Path $tempFile)) { Remove-Item $tempFile -ErrorAction SilentlyContinue }
}
مواضع القراءة ثلاثة.
$_.Exceptionمتن الاستثناء. الخطأ المرفوع بـ-ErrorAction Stopيُلفّ بـActionPreferenceStopException، لكن في مطابقة النوع في catch ينظر المحرّك إلى نوع الاستثناء الأصليّ (مثلItemNotFoundException)، لذا يمكن كتابة catch بتعيين النوع كما هو. يمكن تتبّع ErrorRecord الأصليّ بـ$_.Exception.ErrorRecord.1$_.InvocationInfo.PositionMessageيحمل «أيّ ملفّ وأيّ سطر وأيّ أمر»، وفي سجلّ التنفيذ غير المراقب يتغيّر زمن التحقيق بأرقام حسب وجود هذا.- كتلة finally تُنفَّذ إن نجح try، وإن صار خطأ، وإن أُوقف بـ Ctrl+C. ضع التنظيف مثل إغلاق الاتّصال وحذف الملفّ المؤقّت في finally.7
نظرية التصميم حول أيّ طبقة تلتقط فيها catch وأين تكتب السجلّ مشتركة عبر اللغات. المبادئ التي رتّبناها في «أين ينبغي وضع catch والسجلّ في معالجة الاستثناءات» (الالتقاط عند الحدّ، وعدم الكتم، وتجنّب السجلّ المزدوج) تنطبق على PowerShell كما هي.
4. نجاح الأوامر الأصليّة وفشلها ── $? و$LASTEXITCODE وميّزة 7.4 الجديدة
ثغرة كبيرة أخرى هي الأوامر الأصليّة مثل robocopy وgit وEXE داخل الشركة. البرامج الخارجيّة لا تشارك في نظام أخطاء PowerShell، وتبلّغ الفشل برمز خروج. السلوك الافتراضيّ كالتالي.1
| الحدث | السلوك (افتراضيّ) |
|---|---|
| رمز خروج غير صفريّ | تصير $? $false، ويدخل رمز الخروج في $LASTEXITCODE |
| إنشاء ErrorRecord | لا يتمّ (لا يُضاف أيضاً إلى $Error) |
| try/catch | لا يدخل |
أي أنّ try { robocopy ... } catch { ... } لا يلتقط شيئاً (افتراضياً). يُكتب الحكم على نجاح الأوامر الأصليّة بـ $LASTEXITCODE. $? قيمة منطقيّة لـ «هل نجحت العمليّة الأخيرة»، وبالنسبة للأوامر الأصليّة تصير $true عند رمز الخروج 0 فقط.1 لاحظ أنّه في Windows PowerShell 5.1 كان $? يصير $false بمجرّد كتابة الأمر الأصليّ إلى stderr، أمّا في PowerShell 7 فقد غُيِّر ليصير $false عند رمز خروج غير صفريّ فقط. تغيير يوافق الواقع بأنّ الإخراج إلى stderr لا يُعامَل فشلاً.8
# الأوامر الأصليّة تُحكَم بـ $LASTEXITCODE
robocopy.exe 'D:\Reports' '\\fileserver\reports' /MIR /R:2 /W:5
if ($LASTEXITCODE -ge 8) {
# robocopy: 0–7 نجاح (معلومات عن وجود النسخ إلخ)، 8 فما فوق فشل
throw "فشل robocopy (ExitCode=$LASTEXITCODE)"
}
من PowerShell 7.4 يمكن استخدام $PSNativeCommandUseErrorActionPreference التي تغيّر هذا التعامل. أُضيفت كميّزة تجريبيّة في 7.3، وصارت ميّزة رسميّة (mainstream) في 7.4.3 عند $true يُصدِر الأمر الأصليّ ذو رمز الخروج غير الصفريّ خطأ غير منهِي يوضّح رمز الخروج، وهذا يتبع $ErrorActionPreference. أي أنّ الجمع مع Stop يُدخل فشل الأوامر الخارجيّة أيضاً في try/catch.12
في البيئات المختلطة بين 5.1 و7، تحقّق حتماً من افتراض الإصدار. هذه الميّزة متاحة من PowerShell 7.4 فما بعد، وفي 7.3 كانت ميّزة تجريبيّة (اسم الميّزة PSNativeCommandErrorActionPreference) فكانت تحتاج التفعيل بـ Enable-ExperimentalFeature وإعادة تشغيل الجلسة.3 وفي Windows PowerShell 5.1 لا يوجد هذا المتغيّر نفسه، وتعيين $true لا يفعل شيئاً (يُنشَأ متغيّر جديد فقط، ولا يصير خطأ فيصعب الانتباه). إن أمكن تمرير السكربت نفسه على 5.1 و7 كليهما، فالأسلم عدم الاعتماد على هذه الميّزة، وتوحيد الحكم بـ $LASTEXITCODE بما في ذلك الفصول التالية.
# PowerShell 7.4 فما بعد: عالج فشل الأوامر الخارجيّة أيضاً في try/catch (الافتراض $false)
$PSNativeCommandUseErrorActionPreference = $true
$ErrorActionPreference = 'Stop'
try {
git.exe fetch origin
}
catch {
Write-Warning "فشل git: $($_.Exception.Message)"
throw
}
& {
# الأوامر التي لا يعني فيها غير الصفر فشلاً مثل robocopy تُعطَّل مؤقّتاً
# داخل كتلة سكربت ويُحكَم كالسابق بـ $LASTEXITCODE (تعود عند الخروج)
$PSNativeCommandUseErrorActionPreference = $false
robocopy.exe 'D:\Reports' '\\fileserver\reports' /MIR
if ($LASTEXITCODE -ge 8) { throw "فشل robocopy (ExitCode=$LASTEXITCODE)" }
}
مثال robocopy كما هو وارد في الوثائق الرسميّة، توجد أوامر تستخدم رمز الخروج غير الصفريّ كمعلومة طبيعيّة، لذا إن فُعِّل دفعة واحدة يلزم تصميم نطاق استثناء.2 في المواقع التي لا يمكن فيها إلا Windows PowerShell 5.1، هذه الميّزة غير موجودة فوحِّد الحكم بـ $LASTEXITCODE. فروق السلوك بين 5.1 و7 سهلة أن تصير مطبّاً عند الترحيل، فراجع أيضاً «الفروق بين Windows PowerShell 5.1 وPowerShell 7 والترحيل».
5. تصميم exit code ── حتى يمكن الحكم على النجاح من جدولة المهام والمراقبة
بعد التقاط الخطأ، التالي التبليغ إلى الخارج. وسيلة معرفة جدولة المهام وأدوات المراقبة بنجاح السكربت أو فشله، عملياً، رمز خروج العمليّة فقط. نضبط المواصفة بدقّة.
- يمكن توضيح رمز خروج السكربت بـ
exit <رقم>. يعيّنexitالقيمة أيضاً في$LASTEXITCODE.59 - عند التشغيل بـ
pwsh -File(powershell.exe -File)، تصير القيمة المعيَّنة في exit رمز خروج العمليّة كما هي. بلا عبارة exit، 0 عند الاكتمال الطبيعيّ و1 عند الإنهاء باستثناء غير معالَج.56 - عند تشغيل السكربت بـ
-Command، لا تُحفَظ رموز خروج مثلexit 10داخل السكربت. تُقرَّب إلى 0 أو 1 من نجاح الأمر الأخير (إن كُتبexit 10مباشرة في سلسلة الأمر تُرجَع تلك القيمة). إن كان التشغيل يفرّق برموز خروج السكربت فالقاعدة الثابتة التشغيل بـ-File.6
إن أُسقطت هذه المواصفة هيكلاً، يصير قالب سكربت التنفيذ غير المراقب هكذا.
# Invoke-NightlyExport.ps1 ── هيكل يتيح لجدولة المهام الحكم على النجاح
[CmdletBinding()]
param()
$ErrorActionPreference = 'Stop' # في التنفيذ غير المراقب اجعل «توقّف وأبلِغ» الافتراض
# أبقِ أثر التنفيذ شاملاً الإخراج القياسيّ والخطأ في السجلّ (-Append يلحق بملفّ يوميّ)
Start-Transcript -Path "C:\Logs\NightlyExport_$(Get-Date -Format yyyyMMdd).log" -Append
try {
Export-DailyData # متن معالجة الأعمال (استدعِ دالّة محوَّلة إلى وحدة)
exit 0 # صرِّح بالنجاح
}
catch [System.Net.WebException] {
Write-Warning "خطأ اتصال: $($_.Exception.Message)"
exit 10 # خطأ مؤقّت ── أبقِ مجالاً لإعادة التنفيذ من جانب المهمّة
}
catch {
Write-Warning "خطأ غير متوقَّع: $($_.Exception.Message)"
Write-Warning $_.InvocationInfo.PositionMessage
exit 1 # خطأ دائم ── لا تُعِد التنفيذ ولينظره إنسان
}
finally {
Stop-Transcript # في finally يُغلَق الأثر حتى عبر exit
}
Start-Transcript أمر يسجّل إدخال الجلسة وإخراجها بأكملها نصّاً، فيمكن إعادة إنتاج «ماذا ظهر على الشاشة آنذاك» دون تجهيز echo أو إعادة توجيه.10 ليست متعارضة مع دالة سجلّ خاصّة، ولها قيمة الجمع كحصن أخير. تصميم السجلّ وإجراءات التضخم تناولناها في «تطبيقات PowerShell المتقدّمة ── أتمتة آمنة لتحقيق السجلّات وأرشفتها وإصدار التقارير عنها».
تخصيص exit code الحيلة عدم الإفراط. حبيبات مثل 0=نجاح، 1=خطأ دائم (يراه إنسان)، العشرات=خطأ مؤقّت (يجوز إعادة التنفيذ) كافية، ويمكن بناء حكم النجاح في «نتيجة التشغيل الأخيرة» لجدولة المهام وأدوات إدارة المهام كما هو. إعداد جانب المهمّة (إعادة التنفيذ عند الفشل، وكيفيّة تأكيد نتيجة التنفيذ) راجع «مهمة جدولة المهام لا تُنفَّذ أو تنتهي بـ 0x1 ── فرز السبب وتصميم تشغيل آمن».
6. تصميم إعادة المحاولة ── التمييز بين الخطأ المؤقّت وخطأ الأعمال
أخيراً إعادة التنفيذ. قيمة إعادة المحاولة «امتصاص الأخطاء المؤقّتة تلقائياً وعدم إيقاظ إنسان في منتصف الليل»، لكنّ إدخالها بلا عناية يولّد حوادث أخرى مثل «إعادة محاولة فشل دائم إلى ما لا نهاية» و«معالجة مزدوجة تُفسد البيانات». المبادئ ثلاثة.
- أعد المحاولة للأخطاء المؤقّتة فقط. اقصرها على الفشل الذي قد يحلّه الوقت، مثل انقطاع الشبكة اللحظيّ والقفل المؤقّت للملفّ وانتظار بدء خدمة تابعة. أفشِل فوراً إدخال غير صحيح ونقص صلاحيّات وخطأ إعداد، وسلِّمها لإنسان بـ exit code والسجلّ.
- صمِّم حدّاً أعلى وفاصلاً. قرِّر حدّاً أعلى للمرّات، ووسِّع الفاصل بتراجع أُسّيّ (2 ثانية، 4 ثوانٍ، 8 ثوانٍ…). الطرق المستمرّ بفاصل ثابت على الطرف أثناء العطل يعيق التعافي فقط.
- اجعلها idempotent (آمنة حتى عند إعادة التنفيذ). إعادة المحاولة وإعادة تنفيذ جدولة المهام كلتاهما تعنيان «جري المعالجة نفسها مرّة أخرى». يفترض تصميماً مثل نشر الإخراج بملفّ مؤقّت + إعادة تسمية، وتسجيل المعرِّفات المعالَجة لاستبعاد الاستيراد المزدوج.
كنمط يمكن التجميع في هذا الشكل.
function Invoke-WithRetry {
[CmdletBinding()]
param(
[Parameter(Mandatory)] [scriptblock] $Operation,
# إن مُرِّر 0 أو أقلّ ينتهي بنجاح دون تنفيذ مرّة، لذا أُجبَر 1 فما فوق
[ValidateRange(1, 100)]
[int] $MaxAttempts = 4,
# القيمة السالبة تصير خطأ آخر في Start-Sleep عند إعادة المحاولة، فارفضها عند الربط
[ValidateRange(0, 3600)]
[int] $BaseDelaySeconds = 2,
# اذكر فقط أنواع الاستثناء التي تستحقّ إعادة المحاولة (الافتراض اتصال وI/O)
[Type[]] $RetryableExceptions = @([System.IO.IOException], [System.Net.WebException])
)
for ($attempt = 1; $attempt -le $MaxAttempts; $attempt++) {
try {
# استقبل الإخراج في متغيّر أوّلاً وأرجعه بعد النجاح. إن أُرجع & $Operation مباشرة،
# فإن حدث استثناء بعد إخراج جزئيّ، يسيل الإخراج الجزئيّ إلى المستدعي،
# وعند نجاح إعادة المحاولة يصل نفس البيانات مرّتين
$output = & $Operation
return $output
}
catch {
$ex = $_.Exception
$isRetryable = $RetryableExceptions | Where-Object { $ex -is $_ }
if (-not $isRetryable -or $attempt -eq $MaxAttempts) {
throw # خطأ أعمال، أو حدّ إعادة المحاولة ── أفشِل كما هو
}
# ضع حدّاً أعلى لزمن الانتظار الأُسّيّ (لا تنتظر أكثر حتى مع عدد كبير،
# ولا تتجاوز نطاق قبول Start-Sleep)
$delay = [math]::Min($BaseDelaySeconds * [math]::Pow(2, $attempt - 1), 300)
Write-Warning "فشل (المرّة ${attempt}): $($ex.Message) ── إعادة المحاولة بعد ${delay} ثانية"
Start-Sleep -Seconds $delay
}
}
}
# الاستعمال: ارفع المعالجة المستهدفة إلى خطأ منهِي بـ -ErrorAction Stop
Invoke-WithRetry -Operation {
Copy-Item -Path '\\fileserver\out\daily.csv' -Destination 'D:\Work' -ErrorAction Stop
}
# تنبيه عند إعادة محاولة Invoke-RestMethod / Invoke-WebRequest في PowerShell 7:
# في 7 يصل فشل الاتصال كـ HttpRequestException لا WebException كما في 5.1،
# لذا لا تُعاد المحاولة بالافتراض. واستجابات HTTP الدائمة مثل 404 تصل بالنوع نفسه،
# فإن عادت استجابة ففرِّق بنفسك «هل تستحقّ إعادة المحاولة» برمز الحالة
Invoke-WithRetry -RetryableExceptions ([System.Net.Http.HttpRequestException]) -Operation {
# بـ -SkipHttpErrorCheck استقبل استجابة الخطأ بلا استثناء، ثم ارمِ بحسب الرمز
$r = Invoke-WebRequest -Uri 'https://api.example.co.jp/orders' -TimeoutSec 30 -SkipHttpErrorCheck
if ($r.StatusCode -in 408, 429, 500, 502, 503, 504) {
# ارمِ الرموز المؤقّتة فقط كـ HttpRequestException → تُعاد المحاولة
throw [System.Net.Http.HttpRequestException]::new("خطأ HTTP مؤقّت: $($r.StatusCode)")
}
if ($r.StatusCode -ge 400) {
throw "خطأ HTTP دائم: $($r.StatusCode)" # النوع مختلف فلا تُعاد المحاولة
}
$r.Content | ConvertFrom-Json
}
النقطة اختيار هدف إعادة المحاولة صراحة بنوع الاستثناء. إن كُتب «أعد المحاولة للكلّ إن التُقط في catch»، يُجرَّب حتى خطأ دائم مثل خطأ معامل 4 مرّات ويُنتظَر بلا داعٍ. بعد بدء التشغيل، أسلوب واقعيّ إضافة أنواع الأخطاء المؤقّتة المرصودة في السجلّ الفعليّ إلى $RetryableExceptions.
لاحظ أنّ متن Invoke-WithRetry طويل نسبياً، لذا لا تلصقه في السكربت عند كلّ استخدام، بل احفظه بأكمله في ملفّ مثل Retry.psm1 واقرأه بـ Import-Module. يمنع حوادث مثل اختلاط نسخة قديمة داخل catch فقط عند كلّ نسخ (ممارسة التحويل إلى وحدة راجع «تصميم معاملات PowerShell والتحويل إلى وحدات»). كما أنّ منطق إعادة المحاولة وتفريع الخطأ تحديداً جزء يستحقّ كتابة اختبار بـ Pester («تجهيز اختبارات PowerShell بـ Pester ── نمط عمليّ يجعل سكربتات التشغيل أصعب انكساراً»).
7. القاعدة الثابتة العمليّة (جدول قرار)
| النقطة | الخيارات | مؤشّر الحكم |
|---|---|---|
| السلوك الافتراضيّ للخطأ | الإبقاء على Continue / $ErrorActionPreference = ‘Stop’ في البداية | التنفيذ غير المراقب «التوقّف والإبلاغ» أأمن. سكربت التحقيق التفاعليّ يجوز الإبقاء على Continue2 |
| موضع المراد التقاطه في catch | الدعاء / توضيح -ErrorAction Stop | الأوامر كثيرة الأخطاء غير المنهِية. وضِّح Stop في السطر المراد التقاطه1 |
| نجاح الأوامر الأصليّة | الإهمال / حكم $LASTEXITCODE / $PSNativeCommandUseErrorActionPreference في 7.4 | بيئة مختلطة مع 5.1 وحِّد الحكم بـ $LASTEXITCODE. إن كان 7.4 فما بعد فقط فالميّزة الجديدة + نطاق استثناء لـ robocopy ونحوه32 |
| التبليغ الخارجيّ بالنجاح | السجلّ فقط / تصميم exit code والتشغيل بـ -File | السجلّ للإنسان، وexit code للآلة. كلاهما لازم. التشغيل بـ -Command يسحق رمز الخروج6 |
| أثر التنفيذ | سجلّ خاصّ فقط / الجمع مع Start-Transcript | تأمين يُبقي حتى الإخراج الذي لا يلتقطه السجلّ الخاصّ (الإخراج القياسيّ للأوامر الخارجيّة مثلاً)10 |
| إعادة المحاولة | كلّ الأخطاء هدفاً / قصرها على الأخطاء المؤقّتة + تراجع أُسّيّ + idempotency | إعادة محاولة أخطاء الأعمال مصدر حوادث. مجموعة ثلاث نقاط: حدّ أعلى وفاصل وidempotency |
8. الخلاصة
- تنقسم أخطاء PowerShell إلى خطأ غير منهِي وخطأ منهِي، والخطأ غير المنهي لا يدخل افتراضياً إلى try/catch. القاعدة الثابتة توضيح
-ErrorAction Stopفي الأمر المراد التقاطه. - افتراض
$ErrorActionPreferenceهو Continue. اجعل سكربت التنفيذ غير المراقب Stop في البداية، وامنع هيكلياً حادث «كتم الفشل والاكتمال الطبيعيّ». - فشل الأوامر الأصليّة لا يدخل افتراضياً إلى catch. احكم بـ
$LASTEXITCODE، أو من PowerShell 7.4 فما بعد استفد من$PSNativeCommandUseErrorActionPreference. - في catch أبقِ نوع الاستثناء والرسالة ومعلومات الموضع في السجلّ من
$_(ErrorRecord)، وضع التنظيف في finally. finally تُنفَّذ حتى مع Ctrl+C وexit. - بلِّغ النجاح إلى الخارج بـ exit code. عند التشغيل بـ
-Fileتصير قيمةexitرمز الخروج كما هي، ويمكن الحكم على النجاح من جدولة المهام والمراقبة. - إعادة المحاولة ثلاث مبادئ: قصرها على الأخطاء المؤقّتة، وحدّ أعلى بتراجع أُسّيّ، وidempotency. أفشِل الأخطاء الدائمة فوراً وسلِّمها لإنسان.
مقالات ذات صلة
- تطبيقات PowerShell المتقدّمة ── أتمتة آمنة لتحقيق السجلّات وأرشفتها وإصدار التقارير عنها
- تجهيز اختبارات PowerShell بـ Pester ── نمط عمليّ يجعل سكربتات التشغيل أصعب انكساراً
- مهمة جدولة المهام لا تُنفَّذ أو تنتهي بـ 0x1 ── فرز السبب وتصميم تشغيل آمن
- أين ينبغي وضع catch والسجلّ في معالجة الاستثناءات
- سياسة تنفيذ PowerShell وتوقيع السكربتات
- تصميم معاملات PowerShell والتحويل إلى وحدات
مجالات الاستشارة ذات الصلة
تتعامل شركة كومورا سوفت ذ.م.م. مع مراجعة تصميم معالجة الأخطاء وإعادة المحاولة لسكربتات الدفعة الليليّة والمعالجة الروتينيّة، وتحقيق الأعطال المتقطّعة مثل «يفشل لكنّه يُعامَل نجاحاً» و«ينهار مرّة في الشهر فقط»، وتحسين جودة تشغيل أصول السكربتات القائمة.
- الاستشارة التقنيّة ومراجعة التصميم
- تحقيق الأعطال وتحليل السبب
- ترحيل الأصول القائمة والاستفادة منها
- التواصل معنا
روابط مرجعية
-
Microsoft Learn, about_Error_Handling. حول التصنيف الثلاثيّ للخطأ غير المنهي وخطأ إنهاء العبارة وخطأ إنهاء السكربت، وعدم دخول الخطأ غير المنهي افتراضياً إلى catch/trap، وآليّة الرفع عبر -ErrorAction Stop (ActionPreferenceStopException و$_.Exception.ErrorRecord)، ومطابقة catch بتعيين النوع لنوع الاستثناء الأصليّ، ومواصفات $? و$LASTEXITCODE، وعدم إنشاء رمز الخروج غير الصفريّ للأوامر الأصليّة ErrorRecord افتراضياً، وسلوك $PSNativeCommandUseErrorActionPreference. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17
-
Microsoft Learn, about_Preference_Variables. حول كون القيمة الافتراضيّة لـ $ErrorActionPreference هي Continue، وأولويّة معامل -ErrorAction في الأمر الفرديّ، وتطبيق الإعداد على النطاق والنطاقات الفرعية، وكون القيمة الافتراضيّة لـ $PSNativeCommandUseErrorActionPreference هي $false، ومثال التعطيل المؤقّت داخل كتلة سكربت للأوامر التي تستخدم رمز الخروج غير الصفريّ كمعلومة مثل robocopy. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, What’s New in PowerShell 7.4. حول صيرورة الميّزة التجريبيّة PSNativeCommandErrorActionPreference ($PSNativeCommandUseErrorActionPreference) ميّزة رسميّة (mainstream) في PowerShell 7.4. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Everything you wanted to know about exceptions. حول إمكان الوصول إلى معلومات الاستثناء من $_ داخل كتلة catch، وصيرورة أخطاء الأوامر ذات -ErrorAction Stop وWrite-Error قابلة للتعامل في catch، ونمط تحرير الموارد عبر try/finally. ↩ ↩2
-
Microsoft Learn, about_Language_Keywords. حول تعيين الكلمة المفتاحيّة exit لرمز الخروج وانعكاسه أيضاً في $LASTEXITCODE، وإرجاع السكربت المشغَّل بـ pwsh -File الوسيط الرقميّ لـ exit كرمز خروج، وصيرورته 0 عند الاكتمال الطبيعيّ و1 عند استثناء غير معالَج إن لم توجد عبارة exit. ↩ ↩2 ↩3
-
Microsoft Learn, about_Pwsh. حول كيفيّة تقرّر رمز الخروج عند التشغيل بـ -File، وتحويل رموز الخروج غير 0 و1 إلى 1 عند التشغيل بـ -Command لذا يلزم exit $LASTEXITCODE للحفاظ على رمز الخروج. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, about_Try_Catch_Finally. حول تركيب try/catch/finally، وكتلة catch بتعيين النوع وعدّة catch، وتنفيذ كتلة finally عند النجاح والخطأ إضافة إلى الإيقاف بـ Ctrl+C وexit داخل catch. ↩
-
Microsoft Learn, Differences between Windows PowerShell 5.1 and PowerShell 7.x. حول تغيير PowerShell 7 بحيث لا تصير $? $false بمجرّد كتابة الأمر الأصليّ إلى stderr، بل عند رمز خروج غير صفريّ فقط. ↩
-
Microsoft Learn, about_Automatic_Variables. حول احتفاظ $LASTEXITCODE برمز خروج البرنامج الأصليّ أو السكربت، وتعيين 1 عند الإنهاء باستثناء وقيمة الكلمة المفتاحيّة exit و0 عند الاكتمال الطبيعيّ عند الاستدعاء بـ pwsh -File. ↩
-
Microsoft Learn, Start-Transcript. حول تسجيل أوامر الجلسة وإخراج وحدة التحكّم في ملفّ نصّيّ، والإلحاق بـ -Append، ومكان الحفظ الافتراضيّ واسم الملفّ، والإيقاف بـ Stop-Transcript. ↩ ↩2
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
سياسة تنفيذ PowerShell وتوقيع السكربتات ── دليل عمليّ للخروج من تشغيل «السدّ بـ Bypass»
سياسة تنفيذ PowerShell «جهاز أمان لا حدّ أمنيّ (security boundary)». نرتّب الفروق بين RemoteSigned وغيرها، وأولويّة النطاقات، وMark of th...
لماذا لا تُنفَّذ مهامّ Task Scheduler أو تنتهي بالرمز 0x1 ── تشخيص الأسباب وتصميم تشغيل آمن
نرتّب هنا حساب التشغيل ونوع تسجيل الدخول في Task Scheduler على Windows، والسبب النمطيّ لانتهاء المهمّة بالرمز 0x1، وتفعيل السجلّ (History...
طريقة تشغيل PowerShell من C# (CSharp) واستقبال النتيجة ككائن
نرتّب من منظور عمليّ طريقة تشغيل PowerShell من C# واستقبال النتيجة ككائن PSObject بدل نصّ، بدءًا من PowerShell SDK وAddCommand وAddParame...
تهيئة اختبارات PowerShell عبر Pester ── الأسلوب العمليّ لجعل سكربتات التشغيل أقلّ عرضةً للكسر
نُنظِّم الإجراءات العمليّة لاختبار سكربتات PowerShell عبر Pester v5، بدءاً من معالجة التواريخ وعمليّات الملفّات وعمليّات الحذف، مروراً با...
مجموعة أوامر PowerShell العمليّة ── إضافة وظائف صغيرة شائعة الاستخدام في العمل اليوميّ
نرتّب هنا أوامر PowerShell العمليّة المستخدَمة في العمل اليوميّ، مثل مواضع استخدام Measure-Object وGroup-Object وSelect-String وCompare-O...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
تطوير تطبيقات ويندوز
ندعم تطوير برامج ويندوز للأعمال، وتكامل الأجهزة، وأدوات التواصل.
الأسئلة الشائعة
أسئلة شائعة حول موضوع هذه المقالة.
- لماذا لا يدخل التنفيذ إلى catch رغم كتابة try/catch في PowerShell؟
- لأنّ معظم الأخطاء التي تصدرها الأوامر (cmdlets) أخطاء غير منهِية (non-terminating error). فـ try/catch لا يلتقط سوى الأخطاء المنهِية، أمّا الأخطاء غير المنهِية فتعرض رسالة وتستمرّ في المعالجة، ولا تدخل إلى catch. القاعدة الثابتة للتعامل مع هذا إضافة -ErrorAction Stop إلى الأمر الذي تريد التقاطه (أو ضبط $ErrorActionPreference = 'Stop' في بداية السكربت). فهذا يرفع الخطأ غير المنهي إلى خطأ منهِي، فيصير قابلاً للتعامل معه في try/catch.
- كيف يُفرَّق في الاستخدام بين $? و$LASTEXITCODE؟
- $? قيمة منطقيّة (boolean) تشير إلى نجاح العمليّة الأخيرة، وتُضبَط سواء للأوامر (cmdlets) أم للأوامر الأصليّة (native commands). أمّا $LASTEXITCODE فهو رمز الخروج (exit code) الخاصّ بآخر برنامج أصليّ نُفِّذ (أو سكربت خرج عبر exit)، ولا يتغيّر مع أخطاء الأوامر. عند الحكم على نجاح أو فشل أوامر خارجيّة مثل robocopy أو git، فإنّ الحكم عبر $LASTEXITCODE، الذي يتيح التحقّق حتى من معنى رمز الخروج، هو الأضمن. انتبه إلى أنّ رمز الخروج غير الصفريّ للأوامر الأصليّة لا يدخل افتراضياً إلى catch.
- كيف يمكن الحكم على نجاح سكربت PowerShell أو فشله من Task Scheduler؟
- بتحديد رمز الخروج صراحة عبر الكلمة المفتاحيّة exit في نهاية السكربت (وفي كتلة catch)، وبتشغيل المهمّة عبر pwsh -File (أو powershell.exe -File) من جانب Task Scheduler، ومراقبة قيمة «نتيجة التنفيذ الأخيرة» (Last Run Result). عند التشغيل عبر -File، تصبح القيمة المحدَّدة في exit هي رمز خروج العمليّة نفسه؛ فإن لم يوجد exit يكون الرمز 0 عند الاكتمال الطبيعيّ و1 عند استثناء غير معالَج. أمّا التشغيل عبر -Command فيحوِّل أيّ رمز خروج غير 0 أو 1 إلى 1، لذا فإنّ التشغيل عبر -File هو القاعدة الثابتة إن أردتَ بناء التشغيل على أساس رمز الخروج.
- على أيّ نوع من الأخطاء ينبغي إجراء إعادة المحاولة؟
- اقصرها على الأخطاء المؤقّتة التي قد تتغيّر نتيجتها بإعادة المحاولة (كانقطاع الشبكة اللحظيّ، أو القفل المؤقّت للملفّ، أو انتظار بدء تشغيل خدمة). أمّا أخطاء الأعمال أو الأخطاء الدائمة، مثل بيانات الإدخال غير الصحيحة أو نقص الصلاحيّات أو خطأ في الإعداد، فستفشل مهما أُعيدت المحاولة، لذا لا تُعاد محاولتها بل يُفشَّل الأمر فوراً ويُبلَّغ به الإنسان عبر السجلّ وexit code. وحتى عند إعادة المحاولة، يفترض وضع حدّ أعلى لعدد المرّات والفاصل الزمنيّ، وتوسيع الفاصل عبر التراجع الأُسّيّ (exponential backoff)، وتصميم المعالجة بحيث تكون idempotent (أي لا تسبّب معالجة مزدوجة عند إعادة التنفيذ).
- ما الذي يفعله إعداد $PSNativeCommandUseErrorActionPreference في PowerShell 7.4؟
- إعداد يُصدِر خطأ PowerShell (خطأ غير منهِي) عندما ينتهي أمر أصليّ (native command) برمز خروج غير صفريّ. أُضيف كميّزة تجريبيّة في PowerShell 7.3، وأصبح ميّزة رسميّة في 7.4 (والقيمة الافتراضيّة $false). عند ضبطه على $true فإنّه يتبع $ErrorActionPreference، فإذا جُمِع مع Stop أمكن التقاط فشل الأوامر الخارجيّة عبر try/catch. لكن بما أنّ بعض الأوامر مثل robocopy تستخدم رمز الخروج غير الصفريّ كمعلومة طبيعيّة، فمن الضروريّ التعامل بحذر، كإعادة ضبطه إلى $false في ذلك النطاق فقط.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.