لماذا لا تُنفَّذ مهامّ Task Scheduler أو تنتهي بالرمز 0x1 ── تشخيص الأسباب وتصميم تشغيل آمن
· آخر تحديث: · غو كومورا · Task Scheduler, Windows, PowerShell, أتمتة الأعمال, معالجة الدُفعات, التشغيل, استكشاف الأخطاء وإصلاحها, الاستشارات التقنية
«أريد تشغيل سكربت التجميع الذي أنشأته بـ PowerShell كلّ صباح عند الساعة 6»، «يعمل عند التشغيل يدويّاً، لكنّه لا يعمل عند تحميله على Task Scheduler». عند تلقّي استشارات حول أتمتة الأعمال، ينتهي الأمر تقريباً دائماً بهذا الحديث.
في هذه المدوّنة أيضاً، تناولنا مواضيع الأتمتة تباعاً: أتمتة تنظيم السجلّات، واختبار السكربتات عبر Pester، وتنفيذ PowerShell من C#، وأتمتة الأعمال عبر Power Automate. جميع هذه المقالات تفترض في النهاية «التشغيل الدوريّ عبر Task Scheduler». لكن Task Scheduler نفسه آليّة ذات طبائع غريبة أكثر ممّا يُتوقَّع، وحوادث من نوع «يعمل يدويّاً لكن يفشل في التشغيل الدوريّ» أو «توقّف دون أن يلاحظ أحد» لا تنقطع.
في هذا المقال، نرتّب الأجزاء من آليّة Task Scheduler التي ترتبط مباشرةً بحوادث التشغيل ── حساب التشغيل ونوع تسجيل الدخول، وتشخيص حالة «عدم التنفيذ»، والسبب النمطيّ لقيمة الإرجاع 0x1، وطريقة حفظ السجلّات، والتحكّم في التشغيل المتعدّد ── بالترتيب الذي يكثر التعثّر فيه في العمل الفعليّ.
1. الخلاصة أوّلاً
- معظم مشاكل Task Scheduler لا تنشأ من السكربت نفسه، بل من عدم فهم «كمن، وفي أيّ جلسة يُنفَّذ». بمجرّد اختيار «التشغيل بغضّ النظر عن تسجيل دخول المستخدم»، صمِّم على افتراض أنّه يعمل في عالم مختلف تماماً عن الجلسة التفاعليّة وبيئة تسجيل الدخول.1
- تحقيق حالة «عدم التنفيذ» ينطلق من علامة تبويب السجلّ (History) وسجلّ الأحداث (Event Log). لكن سجلّ المهمّة معطَّل افتراضيّاً، لذا يجب تفعيل «تفعيل سجلّ جميع المهامّ» قبل التشغيل الفعليّ.2
- الرمز
0x1الذي يظهر في «نتيجة التشغيل الأخيرة» ليس خطأً من Task Scheduler، بل يعني أنّ البرنامج الذي تمّ تشغيله أعاد رمز خروج قيمته 1. السبب في جانب السكربت، لذا صمِّم رمز الخروج وابنِ آليّة لحفظ السجلّ بنفسك أوّلاً.3 - الصيغة الأساسيّة لاستدعاء سكربت PowerShell هي
-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "المسار الكامل"، مع بناء المسارات داخل السكربت على$PSScriptRoot. هناك أيضاً فخّ كلاسيكيّ وهو عدم جواز وضع علامات اقتباس في حقل «البدء (اختياريّ)». - لتفادي حادثة توقّف المهمّة صامتة بسبب تغيير كلمة المرور، لا بدّ من تصميم حساب التشغيل (جرد حسابات الخدمة، والنظر في gMSA لبيئة النطاق).4
- التحكّم في التشغيل المتعدّد (الافتراضيّ هو «عدم بدء نسخة جديدة»)، وحدّ مدّة التنفيذ (الافتراضيّ 3 أيّام)، وشرط الطاقة (الافتراضيّ AC فقط)، كلّها إعدادات نموذجيّة يستمرّ التشغيل بها على قيمها الافتراضيّة دون أن يُلاحَظ. حدِّدها صراحةً عند التسجيل دائماً.5
2. بنية Task Scheduler ── المُشغِّل والعمليّة والشروط والإعدادات
تتكوّن مهمّة Task Scheduler من 4 عناصر رئيسيّة.
| العنصر | المحتوى | نقطة قد تسبّب حادثاً |
|---|---|---|
| المُشغِّل (Trigger) | متى يبدأ التشغيل (وقت، عند تسجيل الدخول، عند وقوع حدث، وما إلى ذلك) | كيفيّة التعامل مع فوات مُشغِّل الوقت (StartWhenAvailable المذكور لاحقاً) |
| العمليّة (Action) | ما الذي يُنفَّذ (البرنامج، المعطيات، مجلّد البدء) | أخطاء في وضع علامات الاقتباس على المعطيات، أو في تحديد مجلّد البدء |
| الشروط (Conditions) | هل الوضع مناسب للتنفيذ (الطاقة، الشبكة، الخمول) | تفعيل «AC فقط» افتراضيّاً |
| الإعدادات (Settings) | السلوك أثناء التنفيذ (التشغيل المتعدّد، حدّ الوقت، إعادة المحاولة) | بدء التشغيل الفعليّ دون التحقّق من القيم الافتراضيّة |
يمكن تصدير المهمّة المُنشَأة عبر الواجهة الرسوميّة (taskschd.msc) كملفّ XML. إن أردتَ إدارة تعريف المهمّة عبر Git، أو توزيع نفس المهمّة على عدّة أجهزة، يُنصَح بجعلها سكربتاً عبر تصدير XML مع schtasks /Create /XML، أو عبر وحدة PowerShell ScheduledTasks (New-ScheduledTaskAction / New-ScheduledTaskTrigger / New-ScheduledTaskSettingsSet / Register-ScheduledTask).5
$action = New-ScheduledTaskAction -Execute 'pwsh.exe' `
-Argument '-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\Jobs\Cleanup-Logs.ps1"' `
-WorkingDirectory 'C:\Jobs'
$trigger = New-ScheduledTaskTrigger -Daily -At '06:00'
# القيمة الافتراضيّة لشرط الطاقة هي «البدء فقط عند التشغيل بالطاقة الكهربائيّة (AC) والتوقّف عند التحوّل إلى البطاريّة».
# إذا كانت المهمّة ستعمل أيضاً على حاسوب محمول أو جهاز ميدانيّ، اسمح بذلك صراحةً هنا
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable `
-MultipleInstances IgnoreNew `
-ExecutionTimeLimit (New-TimeSpan -Hours 2) `
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
# استقبل بيانات الاعتماد عبر Get-Credential حتّى لا تظهر كلمة المرور على الشاشة
$cred = Get-Credential -UserName 'DOMAIN\svc-batch' -Message 'بيانات اعتماد حساب التشغيل'
Register-ScheduledTask -TaskName 'KS-Cleanup-Logs' `
-Action $action -Trigger $trigger -Settings $settings `
-User $cred.UserName -Password $cred.GetNetworkCredential().Password
عندما يصبح تعريف المهمّة شيفرة، يمكن نقل ما جُرِّب على جهاز التحقّق مباشرةً إلى بيئة الإنتاج، كما يمكن تفادي حالة «لا نعرف بأيّ إعداد يعمل».
عند النشر على عدّة أجهزة، هناك أيضاً طريقة مُجرَّبة وهي تصدير المهمّة المُعَدّة عبر الواجهة الرسوميّة إلى XML وتوزيعها عبر schtasks.
rem تصدير المهمّة التي أُنشئت على جهاز التحقّق
schtasks /Query /TN "KS-Cleanup-Logs" /XML > KS-Cleanup-Logs.xml
rem الاستيراد على كلّ جهاز (يُحدَّد حساب التشغيل وكلمة المرور عند التسجيل)
schtasks /Create /TN "KS-Cleanup-Logs" /XML KS-Cleanup-Logs.xml /RU DOMAIN\svc-batch /RP *
يحتوي ملفّ XML على كلّ من المُشغِّل والشروط والإعدادات، فإذا وضعتَه في مستودع الشيفرة، يمكن أيضاً مراجعة تعريف المهمّة وإدارة الفروقات (diff). على العكس، فإنّ تسجيل نفس المهمّة يدويّاً عبر الواجهة الرسوميّة على 10 أجهزة يولِّد حتماً «مهمّة شاردة» يختلف إعدادها في جهاز واحد فقط. الأكثر أماناً هو تحويلها إلى شيفرة قبل أن يصل العدد إلى رقمين.
يفترض محتوى هذا المقال Task Scheduler في Windows 10 / 11 وWindows Server 2016 فما بعده (سلسلة Task Scheduler 2.0). في البيئات التي لا تزال تحتفظ بمهامّ أقدم موروثة من أمر at، ابدأ أوّلاً بجرد تلك المهامّ.
3. حساب التشغيل ونوع تسجيل الدخول ── أكثر نقطة تسبّب الأعطال
خياران يُختاران من خصائص المهمّة، «التشغيل فقط عند تسجيل دخول المستخدم» و«التشغيل بغضّ النظر عن تسجيل دخول المستخدم»، هما داخليّاً اختيار نوع تسجيل الدخول (LogonType). إن كان الفهم هنا مختلاً، فسيستمرّ التخبّط دون تفسير معظم حالات «يعمل يدويّاً لكن لا يعمل في التشغيل الدوريّ».1
3.1 الفرق بين الأوضاع الثلاثة
| الاختيار | الآليّة الداخليّة | الخصائص والقيود |
|---|---|---|
| التشغيل فقط عند تسجيل دخول المستخدم | رمز تفاعليّ (InteractiveToken) | تظهر النافذة على الشاشة أثناء تسجيل الدخول. لا يبدأ التشغيل أصلاً أثناء تسجيل الخروج |
| التشغيل بغضّ النظر عن تسجيل الدخول | حفظ كلمة المرور (Password) | تُحفَظ كلمة المرور عند التسجيل. لا تظهر الشاشة (غير تفاعليّ). يفشل التشغيل عند تغيير كلمة المرور |
| نفس ما سبق + «عدم حفظ كلمة المرور» | S4U | لا تُحفَظ كلمة المرور، لكن في المقابل لا يمكن الوصول إلى موارد الشبكة أو الملفّات المشفَّرة (EFS)1 |
الحوادث النمطيّة في العمل الفعليّ هي كالتالي.
- سكربت يصل إلى مجلّد مشترَك سُجِّل بخيار «عدم حفظ كلمة المرور» (S4U). عمل في الاختبار المحليّ، لكن في الإنتاج يفشل الوصول إلى المجلّد المشترَك فقط. ← لأنّ S4U لا يملك بيانات اعتماد شبكيّة.
- بعد عدّة أشهر من التسجيل بخيار «التشغيل بغضّ النظر عن تسجيل الدخول»، حان موعد انتهاء صلاحيّة كلمة مرور النطاق فتمّ تغييرها. منذ ذلك الحين استمرّت المهمّة بالتوقّف بفشل تسجيل الدخول (
0x8007052E)، دون أن يلاحظ أحد. - مهمّة تُشغِّل تطبيقاً برسوميّة سُجِّلت بخيار «بغضّ النظر عن التسجيل». التطبيق يعمل لكن لا تظهر شاشته في أيّ مكان، فحدث سوء فهم بأنّه «لا يعمل». ← لأنّه يعمل في جلسة غير تفاعليّة. العمليّات التي تحتاج شاشة تفاعليّة لا يمكن تشغيلها من حيث المبدأ بهذا التكوين.
كذلك، يحتاج الحساب الذي يُنفَّذ بوضع Password أو S4U إلى حقّ «تسجيل الدخول كمهمّة دفعيّة» (SeBatchLogonRight). يُمنَح هذا الحقّ افتراضيّاً لمجموعة Administrators، لكن عند جعل مستخدم عاديّ مخصَّص حساب خدمة، تحقّق أيضاً من إعداد سياسة الأمان المحليّة.6
3.2 بأيّ حساب نُشغِّل المهمّة
- SYSTEM: لا يحتاج إدارة كلمة مرور، وهو قويّ، لكن صلاحيّاته أقوى من اللازم. مفيد للمعالجات الصيانيّة المحدودة محليّاً، لكن يجب تجنّب جعل SYSTEM حساب أيّ مهمّة تلمس بيانات الأعمال دون تمييز. الفكرة الخاصّة بمتى يحتاج الأمر فعلاً إلى صلاحيّات مسؤول مرتَّبة في مقال منفصل «متى تحتاج تطبيقات Windows فعلاً إلى صلاحيّات المسؤول».
- حساب خدمة مخصَّص (مستخدم عاديّ): يمكن جعله بأدنى الصلاحيّات، لكنّه يتطلّب تحديث المهمّة عند كلّ تغيير لكلمة المرور. أدِر مدّة صلاحيّة كلمة المرور وجرد المهامّ معاً.
- gMSA (حساب الخدمة المُدار جماعيّاً): الخيار الأوّل في بيئة النطاق. بما أنّ كلمة المرور يديرها المتحكّم بالنطاق تلقائيّاً، تختفي مشكلة «توقّف المهمّة بتغيير كلمة المرور» من الأساس. يدعم Task Scheduler التشغيل عبر gMSA.4
كذلك، خانة «التشغيل بأعلى الصلاحيّات» تعني التشغيل بالرمز المميّز الإداريّ (المرفوع) من بين الرمزين المنقسمين بواسطة UAC. لا تفعِّلها في المهامّ التي لا تحتاج صلاحيّات إداريّة.
4. خطوات تشخيص «عدم التنفيذ»
4.1 فعِّل السجلّ (History) قبل الشكّ في أيّ شيء آخر
خيار «تفعيل سجلّ جميع المهامّ» الموجود في اللوحة اليمنى من Task Scheduler معطَّل افتراضيّاً. طالما السجلّ معطَّلاً، لا يبقى حتّى دليل على حدوث الفشل. فعِّله دائماً قبل بدء التشغيل الفعليّ، بحيث يمكن التحقّق منه إلى جانب سجلّ الأحداث (Event Viewer ← Applications and Services Logs ← Microsoft ← Windows ← TaskScheduler ← Operational).2
الخطوات الأساسيّة للتشخيص تعمل جيّداً كما هي من دليل استكشاف الأخطاء وإصلاحها الرسميّ من Microsoft.2
- اختبار السكربت منفرداً ── قبل تحميله على المهمّة، تأكّد من أنّ السكربت نفسه يكتمل بنجاح تحت نفس شروط حساب التشغيل (عبر
runasأو جهاز تحقّق إن أمكن). - مراجعة عمود الحالة وعلامة تبويب السجلّ ── ميّز أوّلاً بين ما إذا تمّ تشغيل المُشغِّل أصلاً، أو تمّ تشغيله لكنّه فشل. إن لم يُشغَّل المُشغِّل، تحقّق من إعداد المُشغِّل والشروط (الطاقة، الشبكة)، وتحقّق مما إذا كانت العمليّة نفسها تعمل عبر التشغيل اليدويّ (نقر بالزرّ الأيمن ← تشغيل).
- جرّب التحويل المؤقّت إلى «التشغيل فقط عند تسجيل دخول المستخدم» ── إن عمل بهذا التحويل، يمكن تضييق السبب إلى الجلسة غير التفاعليّة أو بيانات الاعتماد (الفصل السابق).
4.2 كيفيّة قراءة «نتيجة التشغيل الأخيرة»
| القيمة المعروضة | المعنى |
|---|---|
0x0 |
انتهاء طبيعيّ (أعاد البرنامج المُشغَّل رمز الخروج 0) |
0x1 |
أعاد البرنامج المُشغَّل رمز الخروج 1 (ليس خطأً من Task Scheduler نفسه) |
0x41300 |
في انتظار التشغيل المجدوَل التالي (SCHED_S_TASK_READY) |
0x41301 |
قيد التشغيل حاليّاً (SCHED_S_TASK_RUNNING) |
0x41303 |
لم يُشغَّل ولو مرّة بعد (SCHED_S_TASK_HAS_NOT_RUN) |
0x8007010B |
تحديد غير صحيح لمجلّد البدء («البدء (اختياريّ)»). العرض النمطيّ عند إضافة علامات اقتباس |
0x8007052E |
فشل تسجيل الدخول. كلمة مرور محفوظة قديمة، أو غياب الحقّ اللازم، وما إلى ذلك |
سلسلة 0x413xx هي رموز حالة Task Scheduler نفسه، وسلسلة 0x8007xxxx هي رموز أخطاء Windows، أمّا القيم الصغيرة مثل 0x1 أو 0x2 فهي رمز خروج البرنامج المُشغَّل نفسه.3 عندما يتّضح هذا التمييز، لا تخطئ من البداية في تحديد مكان البحث (إعدادات المهمّة أم السكربت).
4.3 احذر القيم الافتراضيّة للشروط والإعدادات
- شرط الطاقة: «بدء المهمّة فقط عندما يعمل الحاسوب بالطاقة الكهربائيّة (AC)» مفعَّل افتراضيّاً. إن جعلتَ حاسوباً محمولاً جهاز تحقّق، ستحصل على «عطل لا يتكرّر» يظهر فقط عند التشغيل بطاقة البطاريّة. علاوة على ذلك، منذ Windows 10 فما بعده، يتأخّر تشغيل مُشغِّلات معظم المهامّ طالما ميزة توفير البطاريّة مفعَّلة.7
- فوات وقت البدء: إذا كان الحاسوب مطفأً وفات وقت البدء، فبالإعداد الافتراضيّ لا يُنفَّذ حتّى الجدول التالي. إمّا فعِّل صراحةً «إذا تعذّر بدء المهمّة في الوقت المجدوَل، شغِّلها فور توفّر الفرصة» (
-StartWhenAvailable)، أو حدِّد ضمن التصميم ما إذا كانت هذه المهمّة يجوز فواتها.5 - الخروج من السكون: بالنسبة للمهامّ الليليّة على أجهزة تعمل بوضع السكون، حدِّد أيضاً ضرورة «إخراج الحاسوب من السكون لتشغيل المهمّة» (
-WakeToRun).
4.4 احذر في تصميم المُشغِّل (Trigger) نفسه
عند الشعور بأنّ «المهمّة لا تُنفَّذ»، توجد أيضاً حالات يكون فيها تصميم المُشغِّل نفسه مختلفاً عن المقصود.
- «اليوم 31 من كلّ شهر» لا يُنفَّذ في الأشهر التي لا يوجد فيها يوم 31. إن كانت المعالجة لنهاية الشهر، فمن الأسلم توجيه التصميم نحو «آخر يوم من كلّ شهر» (معالجة الشهر السابق في بداية الشهر، أو تحديد التاريخ داخل السكربت نفسه).
- الوقت هو الوقت المحليّ للجهاز الذي سُجِّلت عليه المهمّة. إذا نشرتَ نفس ملفّ XML على أجهزة فروع خارجيّة، أو على خوادم نادراً ما تُدار بإعداد UTC، سيختلف وقت التنفيذ من فرع لآخر. حدِّد ضمن المواصفات ما إذا كان المقصود «الساعة 6 صباحاً بتوقيت اليابان في جميع الفروع» أم «الساعة 6 صباحاً بتوقيت كلّ فرع».
- إذا بدأتَ باستخدام Task Scheduler لتكرار قصير الفواصل (كلّ 5 دقائق مثلاً) فكن حذراً. إنّه أداة ممتازة لـ«دفعة مرّة واحدة يوميّاً»، لكن حين يصبح مطلوباً استطلاع بمستوى الدقائق أو مراقبة مستمرّة، فهذا مجال العمليّة المقيمة (الفصل 8 لاحقاً).
- المُشغِّلات المعتمِدة على الأحداث قويّة، لكن تحقّق أوّلاً هل يُسجَّل الحدث المستهدَف فعلاً باستقرار. التكوين الذي يجعل معرِّف حدث معيّن في سجلّ التطبيق مُشغِّلاً قد يتوقّف عن العمل صامتاً عندما يتغيّر أسلوب ظهور الحدث بعد تحديث التطبيق. المُشغِّل الزمنيّ مع تحديد الشرط داخل السكربت غالباً ما يكون أسهل تتبّعاً في النهاية.
5. الأنماط الشائعة لانتهاء المهمّة بالرمز 0x1 والطريقة الصحيحة لاستدعاء PowerShell
0x1 ليس سوى نتيجة تفيد بفشل السكربت، لذا يكمن السبب في اختلاف بيئة تنفيذ السكربت. الفروق الرئيسيّة بين التشغيل اليدويّ والتشغيل الدوريّ هي كالتالي.
- دليل العمل الحاليّ (current directory) مختلف: إن لم تُحدَّد «البدء (اختياريّ)»، يعمل السكربت في مسار مثل
C:\Windows\System32وما شابه. السكربتات المكتوبة بمسارات نسبيّة تنكسر هنا. يجب على السكربت بناء المسارات استناداً إلى$PSScriptRoot، وعلى جانب المهمّة تحديد مجلّد العمل في «البدء (اختياريّ)». هنا لا يجوز وضع علامات اقتباس في حقل «البدء (اختياريّ)». حتّى المسارات التي تحتوي مسافات تُكتب دون علامات اقتباس (وضعها يؤدّي إلى الفشل بالرمز0x8007010B). - متغيّرات البيئة والملفّ الشخصيّ مختلفة: افترض أنّ متغيّرات البيئة المضبوطة عبر سكربت تسجيل الدخول أو ملفّ المستخدم الشخصيّ، ومحرّكات الأقراص الشبكيّة المرتبطة (مثل X:)، غير موجودة في الجلسة غير التفاعليّة. استخدم مسار UNC (
\\server\share\...) مباشرةً، واستبعد فروقات الملفّ الشخصيّ عبر-NoProfile. - سياسة التنفيذ مختلفة: قد تكون
RemoteSignedمضبوطة للمستخدم، لكن غير مضبوطة لحساب الخدمة. حدِّد-ExecutionPolicy Bypassصراحةً في معطيات المهمّة. - اتفاقيّة رمز الخروج الخاصّة بالأداة غريبة: مثلاً، تُعيد
robocopyالقيمة 1 عندما «توجد ملفّات يجب نسخها ونُسخت بنجاح». إذا كانت الأداة المُغلِّفة (wrapper) تُعيد رمز الخروج كما هو، فقد يبدو الأمر بالرمز0x1رغم أنّه طبيعيّ، أو العكس. تحقّق دائماً من اتفاقيّة رمز الخروج للأمر الخارجيّ المستخدَم.
الصيغة الأساسيّة لاستدعاء PowerShell كالتالي.
البرنامج/السكربت: pwsh.exe (استخدم powershell.exe لـ Windows PowerShell)
إضافة المعطيات: -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\Jobs\Cleanup-Logs.ps1"
البدء (اختياريّ): C:\Jobs (بلا علامات اقتباس)
استخدام -File بدلاً من -Command يجعل هروب (escaping) المعطيات أبسط، بالإضافة إلى أنّ exit n داخل السكربت يصبح رمز خروج العمليّة مباشرةً، ما يتيح تمييز النجاح من الفشل من «نتيجة التشغيل الأخيرة» في Task Scheduler. صمِّم جانب السكربت أيضاً بحيث يُعيد صراحةً 0 عند النجاح وقيمة غير صفريّة عند الفشل.
# اضبط القيمة على Stop حتّى تسقط أيضاً «الأخطاء غير المنهية» الخاصّة بالأوامر (cmdlets) في catch.
# دون هذا، قد يمرّ فشل أوامر مثل Copy-Item دون أن يُلاحَظ وينتهي بـ exit 0
$ErrorActionPreference = 'Stop'
try {
Main
exit 0
}
catch {
Write-Error $_
exit 1
}
الفكرة حول أين نلتقط الاستثناء وكيف نسجِّله مشروحة بالتفصيل أيضاً في مقال منفصل «التقاط الاستثناءات وتصميم السجلّ».
6. اترك السجلّ (Log) بنفسك
سجلّ Task Scheduler لا يخبرنا سوى «هل بدأ التشغيل، وما هو رمز الخروج». أمّا ما الذي فعله السكربت وإلى أيّ حدّ، فيجب على السكربت نفسه تسجيله كسجلّ (log).
كحدّ أدنى، بدلاً من إعادة التوجيه عبر معطيات المهمّة (طريقة إعادة التوجيه في حقل «المعطيات» في Task Scheduler لا تعمل لأنّها لا تمرّ عبر shell)، الأسهل هو أخذ نسخة نصّيّة (transcript) داخل السكربت نفسه.
$logDir = 'C:\Jobs\logs'
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
Start-Transcript -Path (Join-Path $logDir ("cleanup-{0:yyyyMMdd}.log" -f (Get-Date))) -Append
try {
# المعالجة الرئيسيّة
}
finally {
Stop-Transcript
}
بخصوص التعامل مع مشكلة تراكم السجلّات نفسها باستمرار (إدارة الأجيال والأرشفة)، فإنّ ما كُتب بالضبط في «تطبيقات متقدّمة لسكربت PowerShell ── أتمتة تحقيق السجلّات والأرشفة والتقارير بأمان» قابل للاستخدام كما هو.
خطوة أبعد، فكِّر أيضاً في الكتابة إلى سجلّ أحداث Windows. بخلاف سجلّ الملفّات، ميزته أنّ النجاح والفشل يصلان إلى مكان يراقبه فريق التشغيل بالفعل (Event Viewer، وأدوات المراقبة الحاليّة).
# --- تُنفَّذ مرّة واحدة فقط عند الإعداد، بصلاحيّات إداريّة (في برنامج التثبيت أو سكربت الإعداد الأوّليّ) ---
if (-not [System.Diagnostics.EventLog]::SourceExists('KS-Jobs')) {
[System.Diagnostics.EventLog]::CreateEventSource('KS-Jobs', 'Application')
}
# --- متن المهمّة (يعمل بحساب أدنى الصلاحيّات) يقوم بالكتابة فقط.
# قد يتطلّب SourceExists صلاحيّات إداريّة للبحث في كلّ السجلّات، لذا لا تستدعه وقت التشغيل.
# ما يلي من المفترض استدعاؤه داخل معالج فشل Main (catch) ---
catch {
$err = $_
try {
[System.Diagnostics.EventLog]::WriteEntry('KS-Jobs',
"Cleanup-Logs failed: $($err.Exception.Message)",
[System.Diagnostics.EventLogEntryType]::Error, 1001)
}
catch {
# لا تجعل عدم القدرة على الكتابة في السجلّ سبباً لكتم الفشل الأصليّ
Write-Warning "فشل الكتابة إلى سجلّ الأحداث: $_"
}
exit 1
}
توضيحان إضافيّان. أوّلاً، تسجيل مصدر الحدث (CreateEventSource) يحتاج صلاحيّات إداريّة. إذا مُزِج منطق التسجيل في متن المهمّة نفسه، ينتج فشل مضاعف عند أوّل فشل في الإنتاج تحت حساب خدمة بأدنى صلاحيّات: «محاولة التسجيل تُثير استثناءً ← فشل كتابة الحدث الأساسيّ نفسه». يجب فصل التسجيل إلى جانب الإعداد كما في المثال أعلاه، والاكتفاء بالكتابة فقط عند التشغيل. ثانياً، عندما يكون محرِّك التنفيذ pwsh.exe كما في مثال تسجيل المهمّة في هذا المقال، لا يمكن استخدام أوامر New-EventLog / Write-EventLog من عصر Windows PowerShell 5.1 (سيفشل السكربت كاملاً لعدم العثور على الأمر). أمّا استدعاء صنف .NET مباشرةً كما أعلاه فيعمل على 5.1 و7 كليهما.
بعد ذلك، إضافة آليّة واحدة تجعل «الفشل يصل إلى إنسان» ── إشعار عبر البريد الإلكترونيّ أو Teams / Slack ── تمنع حادثة «اكتشاف توقّف استمرّ عدّة أشهر أثناء جرد لاحق». لا حاجة لبنية إشعار معقّدة، فبضعة أسطر تُرسل POST إلى Webhook عند الفشل فقط تكفي تماماً. وعلى العكس، «إشعار عند كلّ نجاح» يتوقّف عن قراءته أحد عاجلاً أم آجلاً، لذا الأنسب هو الاكتفاء بملخّص أسبوعيّ للنجاح، وجعل الهدف من الرصد هو الفشل و«عدم التنفيذ» (تقادم وقت آخر تنفيذ). المعيار الذي يحدّد ما يجب كتابته في السجلّ مرتَّب أيضاً في «التقاط الاستثناءات وتصميم السجلّ».
7. التحكّم في التشغيل المتعدّد والتنفيذ الطويل
ماذا يحدث إذا حان وقت الجدول التالي بينما لا يزال التشغيل السابق مستمرّاً؟ يُحدَّد هذا عبر «القاعدة المُطبَّقة إذا كانت المهمّة قيد التشغيل بالفعل» في علامة تبويب الإعدادات، وتقابلها -MultipleInstances في PowerShell.5
| قيمة الإعداد | السلوك | الاستخدام المناسب |
|---|---|---|
| IgnoreNew (الافتراضيّ في الواجهة الرسوميّة: عدم بدء نسخة جديدة) | تخطّي البدء الجديد إذا كان التشغيل جارياً | عموم الدفعات الدوريّة المتّسمة بالـ idempotency. ابدأ بهذا |
| Queue | التشغيل بالترتيب بعد انتهاء التشغيل الجاري | عمليّات التجميع التي لا تحتمل فقدان أيّ تشغيل |
| Parallel | البدء بالتوازي | تجنَّبه من حيث المبدأ. فقط عند ضمان السلامة عند التوازي |
اضبط أيضاً «الوقت حتّى الإيقاف» (-ExecutionTimeLimit، الافتراضيّ 3 أيّام) بقيمة واقعيّة (نحو ضعفين إلى ثلاثة أضعاف الوقت المتوقّع للتنفيذ)، لمنع حادثة سحب عمليّة معلَّقة (hang) لمهمّة اليوم التالي معها.5
النقطة الواجب الانتباه إليها هي أنّ ما تحميه IgnoreNew أو Queue هو داخل نفس تعريف المهمّة فقط. لا يمنعان التعارض عند استدعاء مهمّة أخرى لنفس السكربت، أو عند تشغيل يدويّ من شخص أثناء الاستجابة لعطل. إذا وُجدت عدّة مسارات تلمس نفس المورد (ملفّ، قاعدة بيانات، نظام خارجيّ)، امنح السكربت نفسه أيضاً آليّة استبعاد متبادل (exclusion). الطريقة النمطيّة هي mutex ذو اسم.
$mutex = New-Object System.Threading.Mutex($false, 'Global\KS-Cleanup-Logs')
if (-not $mutex.WaitOne(0)) {
Write-Warning 'إنهاء التشغيل لأنّ نسخة أخرى قيد التشغيل بالفعل.'
exit 0 # استخدم 0 إذا كان «عدم التنفيذ» لا يُعدّ فشلاً، أو غير 0 إذا كان يُعدّ فشلاً
}
try {
# المعالجة الرئيسيّة
}
finally {
$mutex.ReleaseMutex()
}
إضافة Global\ في بداية الاسم تجعل الاستبعاد المتبادل فعّالاً حتّى بين جلسات مختلفة (مهمّة لمستخدم آخر وتشغيل يدويّ، مثلاً). عليك أن تحدِّد وفق طبيعة المهمّة ما إذا كنت ستنتظر الحصول على القفل (تمرير مهلة زمنيّة إلى WaitOne) أم ستتخلّى فوراً. يجدر الانتباه إلى أنّ الكائن ذا الاسم Global\ مرئيّ لأيّ شخص على الجهاز. في الخوادم المشترَكة التي يسجِّل فيها عدّة مستخدمين الدخول، إذا استحوذ شخص آخر عن نيّة سيّئة أو بالخطأ على mutex بنفس الاسم قبلك، ستستمرّ المهمّة بالتخطّي إلى الأبد (وستبدو ناجحة إن كانت exit 0). في مثل هذه البيئات، اضبط قائمة تحكّم وصول (ACL عبر MutexSecurity) على الـ mutex لتقييد الحسابات القادرة على الحصول عليه، أو على الأقلّ أضف حقيقة «تخطّي بسبب تعذّر الحصول عليه» إلى الإشعار وسجلّ الأحداث من الفقرة السابقة، بحيث يمكن رصد سلسلة التخطّي المتكرّرة عبر المراقبة. التحكّم في الاستبعاد المتبادل عبر تنسيق الملفّات مشروح بالتفصيل في «أفضل الممارسات لتكامل الملفّات والقفل».
8. متى نتوقّف عن استخدام Task Scheduler ── الفصل بينه وبين الخدمة المقيمة
Task Scheduler ليس أداة شاملة لكلّ شيء. عندما تنمو المتطلّبات، توجد حدود ينبغي عندها الانتقال إلى آليّة أخرى بدل الإصرار على الاستمرار به.
| المتطلّب | الآليّة المناسبة |
|---|---|
| دفعة مجدوَلة حتّى بضع مرّات في اليوم | Task Scheduler |
| مُشغِّل التنفيذ خليط من إنسان وحدث ووقت، مع الرغبة في رؤية التدفّق كاملاً | Power Automate (مقال منفصل) |
| استطلاع بمستوى الدقائق، مراقبة مستمرّة، معالجة طوابير | خدمة Windows / عمليّة مقيمة |
| الحاجة إلى الحفاظ على الحالة بين المعالجات، والتحكّم الدقيق في إعادة المحاولة والتراجع (backoff) | خدمة Windows / عمليّة مقيمة |
عند بدء الاستطلاع بـ«مهمّة كلّ 5 دقائق»، يترتّب على كلّ بدء تكلفة إنشاء العمليّة وتحميل الوحدات، بالإضافة إلى حاجة آليّة لحفظ الحالة السابقة في ملفّ ونحوه، فتُعاد فعليّاً برمجة عمليّة مقيمة على شكل قطع صغيرة. عند بلوغ هذه المرحلة، يكون التوطين (الإقامة) عبر Generic Host وBackgroundService في .NET هو الاختيار الطبيعيّ. نمط التنفيذ مشروح في «استخدام Generic Host وBackgroundService في تطبيق سطح مكتب».
على العكس، تحويل دفعة شهريّة أو يوميّة إلى خدمة وإدارة المؤقّت بنفسك مبالغة أيضاً. الحكم الذي لا يخطئ غالباً هو: إن كان «فاصل التنفيذ ساعة فأكثر، والمعالجة مستقلّة، ولا تحمل حالة»، استخدم Task Scheduler، وإذا خرجت عن هذا النطاق، فكِّر بالتوطين.
9. قائمة فحص قبل التشغيل الفعليّ
نوصي بالتحقّق من العناصر التالية مرّة واحدة قبل التسجيل.
- هل حُدِّد حساب التشغيل؟ (هل اخترتَ SYSTEM بدافع الكسل؟ هل نظرتَ في gMSA إن كانت البيئة نطاقاً؟)
- هل فهمتَ قيود نوع تسجيل الدخول؟ (S4U بلا وصول شبكيّ. إن كان Password، هل حُدِّد التشغيل عند تغيير كلمة المرور؟)
- هل اختبرتَ السكربت منفرداً تحت شروط تعادل حساب التشغيل؟
- هل تستدعيه بصيغة
-NoProfile -NonInteractive -ExecutionPolicy Bypass -File؟ - هل المسارات داخل السكربت مبنيّة على
$PSScriptRoot/ UNC؟ (هل تعتمد على محرّك مرتبَط أو مسار نسبيّ؟) - هل خلا حقل «البدء (اختياريّ)» من علامات الاقتباس؟
- هل صُمِّم رمز الخروج؟ (نجاح 0 / فشل غير صفريّ. هل تحقّقتَ من اتفاقيّة رمز الخروج للأمر الخارجيّ؟)
- هل فُعِّل سجلّ المهمّة؟ هل توجد سجلّات السكربت نفسه وإشعار الفشل؟
- هل حُدِّدت صراحةً شروط الطاقة وStartWhenAvailable والتشغيل المتعدّد وحدّ مدّة التنفيذ؟
- هل حُفِظ تعريف المهمّة كملفّ XML أو كسكربت PowerShell في المستودع؟
10. الخلاصة
Task Scheduler ليس «انتهى الأمر بمجرّد كتابة السكربت»، بل لا يستقرّ تشغيله إلّا بعد تصميم ثلاثة افتراضات: حساب التشغيل، والجلسة، والقيم الافتراضيّة. بعبارة أخرى، إن أمسكتَ مرّة واحدة عند التسجيل بالنقاط الواردة في هذا المقال ── اختيار نوع تسجيل الدخول، وتفعيل السجلّ، وتصميم رمز الخروج والسجلّ، والتحديد الصريح للتشغيل المتعدّد وحدّ الوقت ── فلن يحتاج بعدها إلّا القليل جدّاً من العناية.
«يعمل يدويّاً لكن لا يعمل في التشغيل الدوريّ» سببه شبه مؤكّد الفرق بين الجلسة والبيئة. قبل العبث بالإعدادات عشوائيّاً، تحقّق من مدى التقدّم في علامة تبويب السجلّ، وجرِّب خطوات التشخيص الواردة في هذا المقال بالترتيب من الأعلى.
مقالات ذات صلة
- تطبيقات متقدّمة لسكربت PowerShell ── أتمتة تحقيق السجلّات والأرشفة والتقارير بأمان
- حماية سكربت PowerShell عبر Pester ── استراتيجيّة الاختبار في مرحلة الصيانة
- أتمتة الأعمال عبر Power Automate ── الفصل بين التدفّق السحابيّ وتدفّق سطح المكتب وتصميم معالجة الأخطاء
- متى تحتاج تطبيقات Windows فعلاً إلى صلاحيّات المسؤول
مجالات الاستشارة ذات الصلة
تتعامل شركة Komura Soft LLC مع مراجعة تصميم أتمتة الأعمال عبر PowerShell وTask Scheduler، بالإضافة إلى استشارات إعادة بناء المهامّ الدوريّة التي وصلت إلى حالة «تعمل لكن لا أحد يستطيع إصلاحها».
المراجع
-
Microsoft Learn، logonType Simple Type (Task Scheduler). حول تعريف أنواع تسجيل الدخول، وحول أنّ S4U لا تُحفَظ فيه كلمة المرور، لكن في المقابل لا يمكن الوصول إلى الشبكة أو الملفّات المشفَّرة. ↩ ↩2 ↩3
-
Microsoft Learn، Troubleshoot issues with scheduled tasks not running. حول خطوات التشخيص: اختبار السكربت منفرداً ← التحقّق من الحالة والسجلّ ← تغيير خيارات الأمان، وموقع سجلّ أحداث TaskScheduler Operational. ↩ ↩2 ↩3
-
Microsoft Learn، Task Scheduler error and success constants. حول تعريف رموز الحالة والأخطاء مثل SCHED_S_TASK_READY (0x41300) وSCHED_S_TASK_RUNNING (0x41301) وSCHED_S_TASK_HAS_NOT_RUN (0x41303) وما إلى ذلك. ↩ ↩2
-
Microsoft Learn، Manage group Managed Service Accounts. حول إمكانيّة إدارة كلمة مرور gMSA تلقائيّاً من جهة النطاق، ودعم مهامّ Task Scheduler للتشغيل عبر gMSA. ↩ ↩2
-
Microsoft Learn، New-ScheduledTaskSettingsSet. حول معطيات كائن إعدادات المهمّة مثل MultipleInstances (Parallel / Queue / IgnoreNew) وStartWhenAvailable وExecutionTimeLimit (الافتراضيّ 3 أيّام). ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn، Security Contexts for Tasks. حول سياق أمان المهمّة، وحاجة تشغيل المهامّ المسجَّلة بوضع Password أو S4U إلى حقّ «تسجيل الدخول كمهمّة دفعيّة». ↩
-
Microsoft Learn، What’s New in Task Scheduler. حول تأخّر مُشغِّلات المهامّ غير التفاعليّة طالما ميزة توفير البطاريّة مفعَّلة، ابتداءً من Windows 10. ↩
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
معالجة الأخطاء وتصميم إعادة التنفيذ في PowerShell ── من فخّ عدم نجاعة try/catch إلى قواعد exit code وإعادة المحاولة
نرتّب من منظور عمليّ الفرق بين الأخطاء المُنهِية وغير المُنهِية في PowerShell، والفخّ الذي يجعل try/catch غير فعّال والقاعدة الثابتة لـ -...
دليل عملي لأداة Process Monitor (ProcMon) — تحديد سبب «عدم قراءة الإعدادات» و«ACCESS DENIED» خلال 10 دقائق
«عدّلت ملف الإعدادات لكن التغيير لا ينعكس» أو «كان يعمل حتى الأمس لكنه لا يبدأ اليوم» — قبل التدخل في الكود المصدري، يمكن لأداة Process M...
التاريخ والوقت والمناطق الزمنية في تطبيقات الأعمال ── من فخاخ DateTime إلى مبدأ التخزين بتوقيت UTC وتصميم الاختبارات
ينزاح الوقت 9 ساعات بعد نقل الخادم ── نرتّب أسباب أعطال التاريخ والوقت انطلاقًا من خاصيّة Kind في DateTime والتحويل الضمنيّ. نشرح التمييز...
استخدام SQLite في تطبيقات C# للأعمال ── وضع WAL، والتحكّم الحصريّ، والوقاية من التلف، والتمييز عن EF Core
نرتّب هنا المعرفة العمليّة اللازمة لدمج SQLite في تطبيقات الأعمال عبر Microsoft.Data.Sqlite. نشرح وضع WAL، وSQLITE_BUSY وتوحيد مسار الكتا...
كيفيّة إنشاء خدمات Windows وتشغيلها ── من التمييز عن جدولة المهام إلى تحويل BackgroundService إلى خدمة
هل ينبغي تحويل المعالجة المقيمة إلى خدمة Windows، أم تكفي جدولة المهام؟ نرتّب من منظور عمليّ جدول القرار، وكيفيّة إنشاء الخدمة عبر .NET W...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
تطوير تطبيقات ويندوز
ندعم تطوير برامج ويندوز للأعمال، وتكامل الأجهزة، وأدوات التواصل.
الأسئلة الشائعة
أسئلة شائعة حول موضوع هذه المقالة.
- ما الذي يعنيه الرمز 0x1 الذي يظهر في «نتيجة التشغيل الأخيرة» في Task Scheduler؟
- 0x1 ليس خطأً من Task Scheduler نفسه، بل يعني أنّ البرنامج الذي تمّ تشغيله أعاد رمز خروج (exit code) قيمته 1. السبب يكمن في جانب السكربت. الفرق النمطيّ بين التشغيل اليدويّ والتشغيل الدوريّ هو دليل العمل الحاليّ (current directory)، ومتغيّرات البيئة والملفّ الشخصيّ (profile)، وسياسة التنفيذ (execution policy). كما يجب الانتباه إلى اتفاقيّات رمز الخروج الخاصّة ببعض الأوامر الخارجيّة، مثل robocopy الذي يُعيد القيمة 1 حتّى في الحالة الطبيعيّة. معرفة الفرق بين رموز 0x413xx التي هي رموز حالة Task Scheduler نفسه، ورموز 0x8007xxxx التي هي رموز أخطاء Windows، تمنعك من البحث في المكان الخطأ.
- لماذا يعمل الأمر يدويّاً لكنّه لا يعمل عبر Task Scheduler؟
- السبب شبه مؤكَّد أنّه الفرق في حساب التشغيل والجلسة والبيئة. عند اختيار «التشغيل بغضّ النظر عن تسجيل دخول المستخدم»، تعمل المهمّة في جلسة غير تفاعليّة، فلا تكون محرّكات الأقراص الشبكيّة المرتبطة (mapped drives) أو متغيّرات بيئة ملفّ المستخدم الشخصيّ موجودة. علاوة على ذلك، لا يمكن الوصول إلى الموارد على الشبكة عند اختيار «عدم حفظ كلمة المرور» (S4U). خطوات التشخيص الفعّالة هي: التحقّق من علامة تبويب السجلّ (History)، واختبار السكربت منفرداً، والتحويل المؤقّت إلى «التشغيل فقط عند تسجيل الدخول». وبما أنّ سجلّ المهمّة معطَّل افتراضيّاً، يجب تفعيله قبل بدء التشغيل الفعليّ.
- ما الطريقة الصحيحة لاستدعاء سكربت PowerShell من Task Scheduler؟
- حدِّد pwsh.exe (أو powershell.exe) في حقل البرنامج، والصيغة الأساسيّة للمعطيات هي -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "المسار الكامل". استخدام -File بدلاً من -Command يجعل exit n داخل السكربت هو رمز خروج العمليّة مباشرةً، ما يتيح تمييز النجاح من الفشل. اجعل المسارات داخل السكربت مبنيّة على $PSScriptRoot، ولا تضع علامات اقتباس في حقل «البدء (اختياريّ)» (وضعها يؤدّي إلى الفشل بالرمز 0x8007010B). صمِّم السكربت نفسه بحيث يُعيد صراحةً 0 عند النجاح وقيمة غير صفريّة عند الفشل.
- كيف نتجنّب توقّف المهمّة بسبب تغيير كلمة المرور؟
- عند التسجيل بخيار «التشغيل بغضّ النظر عن تسجيل الدخول»، تُحفَظ كلمة المرور، فتستمرّ المهمّة بالتوقّف بفشل تسجيل الدخول (0x8007052E) بعد تغيير كلمة المرور. في بيئة النطاق (domain)، يكون الخيار الأوّل هو gMSA (حساب خدمة مُدار جماعيّاً) الذي يدير فيه المتحكّم بالنطاق كلمة المرور تلقائيّاً، وعندها تختفي هذه المشكلة أصلاً. أمّا عند استخدام حساب خدمة مخصَّص، فأدِر مدّة صلاحيّة كلمة المرور وجرد المهامّ معاً. وأضِف كذلك آليّة تُخطِر عبر البريد الإلكترونيّ أو Teams عند الفشل، لتفادي حادثة عدم ملاحظة توقّف المهمّة لعدّة أشهر.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.
روابط عامة