التعرف على أنبوبة البرنامج النصي PowerShell وإنشاء الوظائف

الأنبوبة في باور شيل هي واحدة من أهم (وأكثرها فائدة) ميزات باور شيل ولغة البرمجة. بمجرد فهمك للأساسيات حول كيفية عملها وما يمكنها القيام به، يمكنك الاستفادة من قوتها في وظائفك الخاصة. في هذا البرنامج التعليمي، ستقوم بذلك بالضبط!

الأنبوبة في باور شيل تتيح لك ربط الأوامر معًا لبناء “أنبوبة” واحدة تبسيطًا للكود، وتمكين المعالجة المتوازية والمزيد. إذا كنت مستعدًا للتعرف على الأنبوبة وبناء وظائفك الخاصة للاستفادة من الأنبوبة، دعنا نبدأ!

المتطلبات المسبقة

سيكون هذا المنشور برنامجًا تعليميًا وستكون جميع العروض التوضيحية تطبيقية. إذا كنت ترغب في متابعة، ستحتاج إلى باور شيل v3+ . سيتم استخدام نافذة التعليمات البرمجية هذه في ويندوز PowerShell v5.1.

فهم أنبوبة باور شيل

معظم الأوامر في باور شيل تستقبل بعض الإدخال عبر معلمة. الأمر يتلقى بعض الكائن كإدخال ويقوم بشيء معين به داخليًا. ثم يُرجع اختياريًا بعض الكائن عبر الإخراج.

الأوامر في الأنبوبة تعمل مثل العدائين البشر في سباق التتابع. يقبل كل عداء في السباق، باستثناء الأول والأخير، العصا (الكائنات) من سابقه ويمررها إلى العداء التالي.

على سبيل المثال، يحتوي الـ Stop-Service cmdlet على معامل يُسمى InputObject. يسمح لك هذا المعامل بتمرير نوع محدد من الكائن إلى Stop-Service الذي يُمثل خدمة Windows التي ترغب في إيقافها.

لاستخدام معامل InputObject، يمكنك استرجاع كائن الخدمة عبر Get-Service ومن ثم تمرير الكائن إلى معامل InputObject كما هو موضح أدناه. هذا الأسلوب لتقديم الإدخال إلى cmdlet Stop-Service عبر معامل InputObject يعمل بشكل رائع وينجز المهمة.

$service = Get-Service -Name 'wuauserv'
Stop-Service -InputObject $service

هذا الأسلوب لتمرير الإدخال إلى أمر Stop-Service يتطلب خطوتين متميزتين. يجب على PowerShell تشغيل Get-Service أولاً ، وحفظ الناتج في متغير ، ثم تمرير تلك القيمة إلى Stop-Service عبر معامل InputObject.

الآن ، قارن الكود أعلاه مع الكود أدناه ، الذي يقوم بنفس العمل. إنه أبسط بكثير لأنه لا يتعين عليك إنشاء متغير $services أو حتى استخدام معامل InputObject على الإطلاق. بدلاً من ذلك ، يعرف PowerShell أنك تنوي استخدام معامل InputObject. يفعل ذلك من خلال مفهوم يُسمى ربط المعاملات.

لقد قمت الآن بـ “ربط” الأوامر معًا باستخدام مشغل |. لقد أنشأت pipeline.

Get-Service -Name 'wuauserv' | Stop-Service

ولكن ليس عليك استخدام فقط أمرين لإنشاء pipeline ؛ يمكنك ربط العديد منها معًا (إذا دعمت معاملات الأوامر ذلك). على سبيل المثال ، يعرض كود الأمثلة أدناه:

  1. يمرر جميع الكائنات التي يُرجعها أمر Get-Service إلى أمر Where-Object.
  2. ينظر بعد ذلك أمر Where-Object إلى خاصية Status لكل كائن ويُرجع فقط تلك الكائنات التي تحتوي على قيمة Running.
  3. ثم، يتم إرسال كل من هذه الكائنات إلى أمر Select-Object، الذي يُرجع فقط خصائص Name و DisplayName للكائنات.
  4. نظرًا لعدم قبول أمر آخر الكائنات التي يُخرجها Select-Object، يتم إرجاع الأوامر مباشرةً إلى وحدة التحكم.

يفهم أمر Where-Object و Select-Object كيفية معالجة إدخال الأنابيب من خلال مفهوم يُسمى ربط المعلمة، والذي يتم مناقشته في القسم التالي.

Get-Service | Where-Object Status -eq Running | Select-Object Name, DisplayName

لمزيد من المعلومات حول الأنابيب، قم بتشغيل الأمر Get-Help about_pipelines.

ربط معلمة الأنبوب

عند النظر الأول إلى الأنبوب، قد يبدو بسيطًا. بعد كل شيء، إنه مجرد تمرير الكائنات من أمر إلى آخر. ولكن في الواقع، الأنبوب أكثر تعقيدًا بكثير. تقبل الأوامر الإدخال عبر المعلمات فقط. يجب على الأنبوب بطريقة ما أن يحدد أي معلمة يجب استخدامها حتى عندما لا تقوم بتحديدها صراحة.

المهمة المتمثلة في معرفة أي معلمة يجب استخدامها عندما تتلقى الأمر إدخالًا عبر الأنبوبة تعرف باسم ربط المعلمة. لربط كائن قادم من الأنبوبة بمعلمة بنجاح، يجب أن تدعم معلمات الأمر الواردة هذا الأمر. تدعم معلمات الأمر ربط المعلمة بالأنبوبة بطريقة واحدة من طريقتين؛ ByValue و/أو ByPropertyName.

ByValue

تقبل معلمة الأمر كامل الكائن الوارد كقيمة للمعلمة. تبحث معلمة ByValue عن كائن من نوع معين في الكائنات الواردة. إذا كان نوع الكائن هو مطابق، يفترض PowerShell أن الكائن معني بأن يكون مرتبطًا بتلك المعلمة ويقبله.

لديها الأمر Get-ChildItem معلمة تسمى Path التي تقبل كائن نصي وإدخال أنبوبة عبر ByValue. وبسبب هذا، يُعيد تشغيل شيء مثل 'C:\Windows' | Get-ChildItem جميع الملفات في دليل C:\Windows لأن C:\Windows هو سلسلة.

ByPropertyName

لا تقبل معلمة الأمر الكائن بأكمله ولكن خاصية واحدة من ذلك الكائن. تقوم بذلك ليس عن طريق النظر إلى نوع الكائن ولكن اسم الخاصية.

يحتوي أمر Get-Process على معلمة Name مُعدة لقبول إدخال الأنابيب بواسطة الخصائص. عند تمرير كائن يحتوي على خاصية Name إلى أمر Get-Process مثل [pscustomobject]@{Name='firefox'} | Get-Process ، يُطابق PowerShell أو يُرتبط بخاصية Name في الكائن الوارد إلى معلمة Name ويستخدم تلك القيمة.

اكتشاف معلمات الأوامر التي تدعم الأنابيب

كما ذُكر سابقًا، لا تدعم كل أمر إدخال الأنابيب. يجب على مؤلف الأمر إنشاء تلك الوظيفة أثناء التطوير. يجب أن يحتوي الأمر على معلمة واحدة على الأقل تدعم الأنابيب، وتحديدًا ByValue أو ByPropertyName.

كيف تعرف أي الأوامر ومعلماتها تدعم إدخال الأنابيب؟ يمكنك مجرد تجربتها بالتجربة والخطأ، ولكن هناك طريقة أفضل باستخدام نظام مساعدة PowerShell باستخدام أمر Get-Help.

Get-Help <COMMAND> -Parameter <PARAMETER>

على سبيل المثال، انظر إلى معلمة Path لأمر Get-ChildItem أدناه. يمكنك رؤية أنها تدعم كلاً من أنواع إدخال الأنابيب.

PowerShell pipeline input is allowed

بمجرد معرفتك بالمعلمات التي تدعم إدخال الأنابيب، يمكنك ثم الاستفادة من تلك الوظيفة، كما هو موضح أدناه.

# لا يدعم الاستدعاء بدون أنابيب
Get-ChildItem -Path 'C:\\Program Files', 'C:\\Windows'

# استدعاء بأنابيب
'C:\\Program Files', 'C:\\Windows' | Get-ChildItem

لكن، من ناحية أخرى، لا يدعم معلمة DisplayName على Get-Service إدخال الأنابيب.

PowerShell pipeline input is not allowed

بناء وظيفة أنبوب بنفسك

رغم أن المعالجات القياسية لـ PowerShell تدعم إدخال الأنابيب لا يعني ذلك أنه لا يمكنك الاستفادة من تلك الوظيفة. لحسن الحظ، يمكنك بناء وظائف تقبل إدخال الأنابيب أيضًا.

لتوضيح، دعنا نبدأ بوظيفة موجودة تسمى Get-ConnectionStatus.

  • تحتوي هذه الوظيفة على معلمة واحدة (لا تقبل إدخال الأنابيب) تسمى ComputerName، والتي تسمح لك بتمرير سلسلة واحدة أو أكثر إليها. يمكنك ملاحظة أن معلمة ComputerName لا تقبل إدخال الأنابيب لأنها لم تُعرف كسمة للمعلمة ([Parameter()]).
  • ثم تقرأ الوظيفة كل واحدة من تلك السلاسل وتشغل معالج الأوامر Test-Connection ضد كل واحدة منها.
  • لكل اسم كمبيوتر مرر، يعيد ثم كائنًا يحتوي على خاصية ComputerName وخاصية Status.
function Get-ConnectionStatus
{
    [CmdletBinding()]
    param
    (
        [Parameter()] ## لا توجد إدخال من الأنبوبة
        [string[]]$ComputerName
    )

    foreach($c in $ComputerName)
    {
        if(Test-Connection -ComputerName $c -Quiet -Count 1)
        {
            $status = 'Ok'
        }
        else
        {
            $status = 'No Connection'
        }

        [pscustomobject]@{
            ComputerName = $c
            Status = $status
        }
    }
}

ثم يمكنك استدعاء Get-ConnectionStatus عن طريق تمرير معلمة ComputerName واحدة أو أكثر من أسماء المضيفين أو عناوين IP مثلما هو موضح أدناه.

Get-ConnectionStatus -ComputerName '127.0.0.1', '192.168.1.100'

الخطوة 1: السماح بإدخال من الأنبوبة

لتمكين هذه الوظيفة من قبول إدخال من الأنبوبة، يجب عليك أولاً تحديد السمة المناسبة للمعلمة. يمكن أن تكون هذه السمة إما ValueFromPipeline لقبول الإدخال من الأنبوبة بالقيمة أو ValueFromPipelineByPropertyName لقبول الإدخال من الأنبوبة بواسطة اسم الخاصية.

لهذا المثال، أضف سمة ValueFromPipeline داخل القوسين لتعريف [Parameter()] كما هو موضح أدناه.

 [Parameter(ValueFromPipeline)]
 [string[]]$ComputerName

في هذه النقطة، هذا كل ما عليك فعله من الناحية الفنية. وظيفة Get-ConnectionStatus ستربط الآن أي كائن نصي يتم تمريره إليه بالمعلمة ComputerName. ومع ذلك، حتى وإن كان ربط المعلمة يحدث لا يعني أن الوظيفة ستقوم بأي شيء ذو معنى معه.

الخطوة 2: إضافة كتلة عمل

عندما تريد باور شيل معالجة جميع الكائنات القادمة من الأنبوبة، يجب عليك إضافة كتلة Process للتنفيذ. تخبر هذه الكتلة باور شيل بتنفيذ عملية كل كائن قادم من الأنبوبة.

بدون كتلة Process، سوف يقوم باور شيل فقط بمعالجة أول كائن قادم من الأنبوبة. تخبر كتلة Process باور شيل بمواصلة معالجة الكائنات.

function Get-ConnectionStatus
{
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline)]
        [string[]]$ComputerName
    )

    Process ## كتلة عمل جديدة
    {
        foreach($c in $ComputerName)
        {
            if(Test-Connection -ComputerName $c -Quiet -Count 1)
            {
                $status = 'Ok'
            }
            else
            {
                $status = 'No Connection'
            }

            [pscustomobject]@{
                ComputerName = $c
                Status = $status
            }
        }
    } ## نهاية كتلة العمل
}

يرجى إرسال الإخراج دائمًا من داخل كتلة Process. إرسال الإخراج من داخل كتلة Process يقوم بتيار الكائنات إلى الأنبوب مما يتيح للأوامر الأخرى قبول تلك الكائنات من الأنبوب.

تمرير الكائنات إلى أنبوب PowerShell

بمجرد تحديد كتلة Process في الوظيفة أعلاه، يمكنك الآن استدعاء الوظيفة وتمرير القيم إلى معلمة ComputerName عبر الأنبوب كما هو موضح أدناه.

Get-ConnectionStatus -ComputerName '127.0.0.1', '192.168.1.100'
## أو
'127.0.0.1', '192.168.1.100' | Get-ConnectionStatus

في هذه النقطة، يمكنك الاستفادة من قوة الأنبوب الحقيقية وبدء دمج المزيد من الأوامر في المزيج. على سبيل المثال، ربما لديك ملف نصي، C:\Test\computers.txt، مع سطر من عناوين IP مفصولة عبر سطر جديد مثلما يلي.

127.0.0.1
192.168.1.100

ثم يمكنك استخدام cmdlet Get-Content لقراءة كل من تلك العناوين IP في الملف النصي وتمريرها مباشرة إلى الوظيفة Get-ConnectionStatus.

Get-Content -Path C:\Test\computers.txt | Get-ConnectionStatus 

أخذ هذا الإعداد خطوة أبعد، يمكنك توجيه الكائنات التي تعيدها Get-ConnectionStatus مباشرة إلى cmdlet ForEach-Object.

الكود أدناه:

  • يقوم بقراءة جميع أسماء الحواسيب في ملف النص ويمررها إلى وظيفة Get-ConnectionStatus.
  • Get-ConnectionStatus تعالج كل اسم للحاسوب وتعيد كائنًا يحتوي على الخصائص ComputerName و Status.
Get-Content -Path C:\Test\computers.txt |
Get-ConnectionStatus |
ForEach-Object { Write-Host "$($_.ComputerName) connection status is: $($_.Status)" -ForegroundColor Cyan }

Get-ConnectionStatus تمرر بعد ذلك كل كائن إلى cmdlet ForEach-Object، الذي يعيد سلسلة واحدة ملونة باللون السماوي مع حالة قابلة للقراءة للإنسان.

ربط الأنابيب عن طريق اسم الخاصية

حتى الآن، تم تكوين cmdlet Get-ConnectionStatus لقبول إدخال الأنبوب بالقيمة (ValueFromPipeline) عن طريق قبول مصفوفة من السلاسل مثل '127.0.0.1', '192.168.1.100'. هل ستعمل هذه الوظيفة كما هو متوقع إذا كان الإدخال مأخوذًا من ملف CSV بدلاً من ملف نصي لعناوين IP؟

ربما لديك ملف CSV يبدو كما هو موضح أدناه في C:\Test\pc-list.csv.

ComputerName,Location
127.0.0.1,London
192.168.1.100,Paris

يرجى ملاحظة أن حقل ComputerName في ملف CSV هو نفس اسم ComputerName في Get-ConnnectionStatus ComputerName parameter.

إذا كنت تحاول استيراد الملف CSV وتمريره عبر الأنبوب إلى Get-ConnectionStatus، ستعيد الوظيفة نتيجة غير متوقعة في عمود ComputerName.

Import-Csv -Path 'C:\Test\pc-list.csv' | Get-ConnectionStatus

ComputerName                                  Status       
------------                                  ------       
@{ComputerName=127.0.0.1; Location=London}    No Connection
@{ComputerName=192.168.1.100; Location=Paris} No Connection

هل يمكنك تخمين ما الذي حدث خطأ؟ بعد كل شيء، اسم المعلمة متطابق، لذلك لماذا لم تربط أنبوبة PowerShell الإخراج الذي أرجعته Import-CSV بالمعلمة ComputerName في Get-ConnectionStatus؟ لأنك بحاجة إلى سمة المعلمة ValueFromPipelineByPropertyName.

في الوقت الحالي، لدى معلمة ComputerName في الوظيفة تعريف معلمة يبدو على النحو التالي [Parameter(ValueFromPipeline)]. لذلك، يجب عليك إضافة ValueFromPipelineByPropertyName لتعيين معلمة ComputerName لدعم الإدخال بالاسم، كما هو موضح أدناه.

    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
        [string[]]$ComputerName
    )

بمجرد أن يكون لديك دعم أنبوبة بواسطة الاسم، تخبر PowerShell بالبدء في النظر إلى أسماء خصائص الكائن ونوع الكائن. بمجرد أن تقوم بهذا التغيير، يجب أن ترى الإخراج المتوقع بعد ذلك.

PS> Import-Csv -Path 'C:\Test\pc-list.csv' | Get-ConnectionStatus

ComputerName  Status       
------------  ------       
127.0.0.1     Ok           
192.168.1.100 No Connection

ملخص

في هذا البرنامج التعليمي، تعلمت كيفية عمل أنبوبة PowerShell، وكيفية ربط المعلمات، وحتى كيفية إنشاء وظيفة خاصة بك تدعم أنبوبة PowerShell.

على الرغم من أن الوظائف ستعمل بدون الأنبوبة، إلا أنها لن تقوم بـ “تيار” الكائنات من أمر إلى آخر وتبسيط الكود.

هل يمكنك التفكير في وظيفة قمت بكتابتها، أو ربما على وشك كتابتها، التي يمكن أن تستفيد من جعلها جاهزة للأنبوبة؟

Source:
https://adamtheautomator.com/ppowershell-pipeline/