# PowerShell 7.x - قسمت نهم - آشنایی با Crescendo

همانطور که در ابتدای این سری نیز اشاره شد، یکی از ویژگی‌های منحصربه‌فرد PowerShell، طراحی شیءگرای آن است، به‌طوریکه خروجی cmdletهای آن، به صورت آبجکت هستند. همچنین، در PowerShell امکان اجرای کامندهای 

- Published: 2023-04-02
- Language: fa
- Tags: DNTips
- Canonical: https://sirwan.info/blog/fa/dntips-3464

---

> این نوشته نخستین بار در [دات‌نت تیپس](https://www.dntips.ir/post/3464) منتشر شده است.

<div class="postBody">همانطور که <a href="https://vahidn.github.io/dntips.mirror/OPF/www.dntips.ir-learning-paths-toc-page-1.html">در ابتدای این سری</a>  نیز اشاره شد، یکی از ویژگی‌های منحصربه‌فرد PowerShell، طراحی شیءگرای آن است، به‌طوریکه خروجی cmdletهای آن، به صورت آبجکت هستند. همچنین، در PowerShell امکان اجرای کامندهای native نیز وجود دارد. به عنوان مثال اگر کامند زیر را وارد کنید:  <div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">git log --oneline</pre>
 </div> <div>خروجی، همانطوری که در دیگر shellها انتظار میرود، نمایش داده خواهد شد؛ یعنی به صورت string. همچنین امکان intellisense را نیز برای پارامترهای کامند موردنظر نخواهیم داشت؛ چون در اصل، به اصطلاح یک legacy command است و نه یک cmdlet. برای بهره بردن از امکانات PowerShell میتوانیم این نوع کامندها را توسط یک wrapper به cmdlet تبدیل کنیم، اما آپدیت نگه‌داشتن این wrapper و نوشتن آن فرآیند سختی است. برای سهولت انجام اینکار، یک فریم‌ورک تحت عنوان <a href="https://github.com/PowerShell/Crescendo">Crescendo</a> توسط مایکروسافت ارائه شده است.</div> <div> <b>یک مثال</b> </div> <div>فرض کنید میخواهیم کامند git log را به همراه تعدادی از دستورات آن به یک PowerShell cmdlet تبدیل کنیم؛ برای اینکار ابتدا نیاز است ماژول عنوان شده را نصب کنیم:  </div> <div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">Install-Module -Name Microsoft.PowerShell.Crescendo</pre>
 </div>
بعد از نصب ماژول فوق، یکسری cmdlet به مجموعه کامندهای PowerShell اضافه خواهند شد. یکی از این کامندها New-CrescendoCommand است. با کمک این کامند، فایل JSON موردنیاز Crescendo را میتوانیم تولید کنیم:  <br/> </div> <div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">$Configuration = @{&#10;    '$schema' = "https://aka.ms/PowerShell/Crescendo/Schemas/2021-11"&#10;    Commands  = @()&#10;}&#10;$parameters = @{&#10;    Verb = "Get"&#10;    Noun = "GitLog"&#10;    OriginalName = "git"&#10;}&#10;$Configuration.Commands += New-CrescendoCommand @parameters&#10;&#10;$Configuration | ConvertTo-Json -Depth 3 | Out-File ./git-ps.json</pre>
 </div>
در اینجا تعیین کرده‌ایم که کامندی که میخواهیم برایمان تولید شود، چه ویژگی‌هایی باید داشته باشد. به عنوان مثال Verb آن Get و Noun آن باید GitLog باشد (<a href="https://learn.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands?view=powershell-7.3">براساس استانداری که مایکروسافت برای نامگذاری cmdletها پیشنهاد میدهد</a>). در نهایت میتوانیم به صورت Get-GitLog از آن استفاده کنیم. همچنین legacy command اصلی که میخواهیم برای آن cmdlet ایجاد کنیم نیز توسط OriginalName تعیین شده‌است. لازم به ذکر است که در ویندوز باید مسیر کامل آن را وارد کنید. سپس با اجرای دستورات فوق، خروجی زیر برایمان تولید خواهد شد:  <br/> </div> <div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">{&#10;  "Commands": [&#10;    {&#10;      "Verb": "Get",&#10;      "Noun": "GitLog",&#10;      "OriginalName": "git",&#10;      "OriginalCommandElements": null,&#10;      "Platform": [&#10;        "Windows",&#10;        "Linux",&#10;        "MacOS"&#10;      ],&#10;      "Elevation": null,&#10;      "Aliases": null,&#10;      "DefaultParameterSetName": null,&#10;      "SupportsShouldProcess": false,&#10;      "ConfirmImpact": null,&#10;      "SupportsTransactions": false,&#10;      "NoInvocation": false,&#10;      "Description": null,&#10;      "Usage": null,&#10;      "Parameters": [],&#10;      "Examples": [],&#10;      "OriginalText": null,&#10;      "HelpLinks": null,&#10;      "OutputHandlers": null&#10;    }&#10;  ],&#10;  "$schema": "https://aka.ms/PowerShell/Crescendo/Schemas/2021-11"&#10;}</pre>
 </div> <b>
نکته</b>: دقت داشته باشید که schema$ باید درون single quote نوشته شود؛ چون در غیراینصورت، key آن درون فایل تولید شده، خالی خواهد بود:  <br/> </div> <div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">"": "https://aka.ms/PowerShell/Crescendo/Schemas/2021-11",</pre>
 </div>
با کمک این schema درون Visual Studio Code امکان Intelisense را نیز خواهیم داشت:  <br/> </div> <div> <p style="margin-left: auto; margin-right: auto;"> <img src="/img/dntips/d1be0d6b2f201c00a23f.jpg" style="display: block; margin-left: auto; margin-right: auto; cursor: default; height: 285px;"/> </p> <p style="margin-left: auto; margin-right: auto;"> <br/> </p> <p style="margin-left: auto; margin-right: auto;">اکنون باید این فایل Configuration را به Crescendo معرفی کنیم تا cmdlet را برایمان تولید کند. اینکار را توسط Export-CrescendoModule انجام خواهیم داد:  <br/> </p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">Export-CrescendoModule -Configuration ./git-ps.json -ModuleName ./git-ps.psm1</pre>
 </div> <p style="margin-left: auto; margin-right: auto;">با اجرای دستور فوق، فایل‌های git.psm1 و همچنین git.psd1 تولید خواهند شد. نیاز به بررسی فایل‌های جنریت شده نیست؛ چون تنها جایی که با آن باید در ارتباط باشیم، همان فایل JSON ابتدای بحث است که در ادامه آن را بررسی خواهیم کرد. اما قبل از آن اجازه دهید ماژول تولید شده را Import کنیم و دستور Get-GitLog را وارد کنیم:  <br/> </p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">PP /&gt; Import-Module ./git-ps.psd1&#10;PS /&gt; Get-GitLog&#10;&#10;usage: git [-v | --version] [-h | --help] [-C &lt;path&gt;] [-c &lt;name&gt;=&lt;value&gt;]&#10;           [--exec-path[=&lt;path&gt;]] [--html-path] [--man-path] [--info-path]&#10;           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]&#10;           [--git-dir=&lt;path&gt;] [--work-tree=&lt;path&gt;] [--namespace=&lt;name&gt;]&#10;           [--super-prefix=&lt;path&gt;] [--config-env=&lt;name&gt;=&lt;envvar&gt;]&#10;           &lt;command&gt; [&lt;args&gt;]&#10;&#10;These are common Git commands used in various situations:&#10;&#10;start a working area (see also: git help tutorial)&#10;   clone     Clone a repository into a new directory&#10;   init      Create an empty Git repository or reinitialize an existing one&#10;&#10;work on the current change (see also: git help everyday)&#10;   add       Add file contents to the index&#10;   mv        Move or rename a file, a directory, or a symlink&#10;   restore   Restore working tree files&#10;   rm        Remove files from the working tree and from the index&#10;&#10;examine the history and state (see also: git help revisions)&#10;   bisect    Use binary search to find the commit that introduced a bug&#10;   diff      Show changes between commits, commit and working tree, etc&#10;   grep      Print lines matching a pattern&#10;   log       Show commit logs&#10;   show      Show various types of objects&#10;   status    Show the working tree status&#10;&#10;grow, mark and tweak your common history&#10;   branch    List, create, or delete branches&#10;   commit    Record changes to the repository&#10;   merge     Join two or more development histories together&#10;   rebase    Reapply commits on top of another base tip&#10;   reset     Reset current HEAD to the specified state&#10;   switch    Switch branches&#10;   tag       Create, list, delete or verify a tag object signed with GPG&#10;&#10;collaborate (see also: git help workflows)&#10;   fetch     Download objects and refs from another repository&#10;   pull      Fetch from and integrate with another repository or a local branch&#10;   push      Update remote refs along with associated objects&#10;&#10;'git help -a' and 'git help -g' list available subcommands and some&#10;concept guides. See 'git help &lt;command&gt;' or 'git help &lt;concept&gt;'&#10;to read about a specific subcommand or concept.&#10;See 'git help git' for an overview of the system.</pre>
 </div> <p style="margin-left: auto; margin-right: auto;">همانطور که مشاهده میکنید، خروجی دستور git، نمایش داده شده‌است. دلیل آن نیز این است که در فایل configuration، هیچ آرگومانی را به عنوان ورودی آن تعیین نکرده‌ایم. برای اضافه کردن آرگومان‌های موردنظر باید پراپرتی OrginalCommandElements را مقدار دهی کنیم:  <br/> </p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">"OriginalCommandElements": ["log", "--oneline"],</pre>
 </div> <p style="margin-left: auto; margin-right: auto;">بنابراین با فراخوانی دستور Get-GitLog، در اصل دستور git log —oneline فراخوانی خواهد شد:   <br/> </p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">PS /&gt; Get-GitLog&#10;&#10;e9590e8 init</pre>
 </div> <p style="margin-left: auto; margin-right: auto;">اما تا اینجا نیز خروجی به صورت رشته‌ایی است. برای داشتن یک خروجی Object، باید پراپرتی OutputHandlers را از Configuration، تغییر دهیم:  <br/> </p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">"OutputHandlers": [&#10;  {&#10;    "ParameterSetName": "Default",&#10;    "Handler": "$args[0] | ForEach-Object { $hash, $message = $_.Split(' ', 2) ; [PSCustomObject]@{ Hash = $hash; Message = $message } }"&#10;  }&#10;]</pre>
 </div> <p style="margin-left: auto; margin-right: auto;">در اینجا توسط args$ به خروجی کامند اصلی دسترسی خواهیم داشت. این خروجی را سپس با کمک ForEach-Object، به یک شیء با پراپرتی‌های Hash و Message تبدیل کرده‌ایم. در اینجا فقط میخواستم روال تهیه یک آبجکت را از کامندهایی که خروجی JSON ندارند، نشان دهم؛ اما خوشبختانه توسط پرچم pretty در git log، امکان تهیه‌ی خروجی JSON را نیز داریم:  <br/> </p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">git log --pretty=format:'{"commit": "%h", "author": "%an", "date": "%ad", "message": "%s"}'</pre>
 </div> <p style="margin-left: auto; margin-right: auto;">در نتیجه عملاً نیازی به split کردن نیست و بجای آن میتوانیم به صورت مستقیم، خروجی را توسط ConvertFrom-Json پارز کنیم:  <br/> </p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">"OutputHandlers": [&#10;  {&#10;    "ParameterSetName": "Default",&#10;    "Handler": "$args[0] | ConvertFrom-Json"&#10;  }&#10;]</pre>
 </div> <p style="margin-left: auto; margin-right: auto;">همچنین درون فایل schema با کمک پراپرتی Parameters، امکان تعریف پارامتر را نیز برای کامند Get-GitLog خواهیم داشت. به عنوان مثال میتوانیم فلگ reverse را نیز به کامند اصلی از طریق PowerShell ارسال کنیم:  <br/> </p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">"Parameters": [&#10;  {&#10;    "Name": "reverse",&#10;    "OriginalName": "--reverse",&#10;    "ParameterType": "switch",&#10;    "Description": "Reverse the order of the commits in the output."&#10;  }&#10;],</pre>
 </div> <p style="margin-left: auto; margin-right: auto;">دقت داشته باشیم که با هربار تغییر فایل schema باید توسط دستور Export-CrescendoModule ماژول موردنظر را تولید کنید: </p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">Export-CrescendoModule -Configuration ./git-ps.json -ModuleName ./git-ps.psm1&#10;Import-Module ./git-ps.psd1</pre>
 </div> <p style="margin-left: auto; margin-right: auto;">در نهایت cmdletمان به این صورت قابل استفاده خواهد بود:</p> <p style="margin-left: auto; margin-right: auto;"> <img src="/img/dntips/48f5cca0150a034f81fd.jpg" style="display:block; margin-left: auto; margin-right: auto;"/> </p> </div> </div></div>
