Skip to content

Commit 29fe5bc

Browse files
authored
Merge pull request #6059 from vladimir-shcherbakov/iss#865
[new-doc] how to generate and use format.ps1xml file (iss #865)
2 parents e32b143 + 637aeee commit 29fe5bc

File tree

1 file changed

+365
-0
lines changed

1 file changed

+365
-0
lines changed
Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
# Introduction
2+
Output is the most important part of any interactive console application including Powershell. PowerShell has a set of [format cmdlets](https://docs.microsoft.com/en-us/powershell/scripting/getting-started/cookbooks/using-format-commands-to-change-output-view?view=powershell-6) that allow you to control the cmdlet output format:
3+
1. Format-Wide
4+
2. Format-List
5+
3. Format-Table
6+
4. Format-Custom
7+
8+
Each format cmdlet has default properties that will be used if you do not specify specific properties to display. Each cmdlet also uses the same parameter name, **Property**, to specify which properties you want to display.
9+
10+
Our team trends to make the cmdlets output more convenient and consistent across all the resource providers and chasing the following goals:
11+
1. Default output for cmdlets should be displayed in a table view.
12+
2. Output should include only essential properties with clear labels.
13+
14+
15+
# How table view output works by default.
16+
17+
As an example let's consider [Get-AzureRmSubscription](https://github.com/Azure/azure-powershell/blob/preview/src/ResourceManager/Profile/Commands.Profile/Subscription/GetAzureRMSubscription.cs) cmdlet.
18+
19+
The cmdlet class specifies the ```PSAzureSubscription``` class as an output type with the **OutputType attribute**:
20+
21+
```Cs
22+
namespace Microsoft.Azure.Commands.Profile
23+
{
24+
[Cmdlet(VerbsCommon.Get, "AzureRmSubscription", DefaultParameterSetName = ListByIdInTenantParameterSet),
25+
OutputType(typeof(PSAzureSubscription))]
26+
public class GetAzureRMSubscriptionCommand : AzureRmLongRunningCmdlet
27+
{
28+
public const string ListByIdInTenantParameterSet = "ListByIdInTenant";
29+
public const string ListByNameInTenantParameterSet = "ListByNameInTenant";
30+
31+
// omitted for brevity the rest of the definition.
32+
```
33+
34+
The [PSAzureSubscription](https://github.com/Azure/azure-powershell/blob/preview/src/ResourceManager/Common/Commands.Common.Authentication.ResourceManager/Models/PSAzureSubscription.cs) class contains several public properties.
35+
36+
* Id
37+
* Name
38+
* State
39+
* SubscriptionId
40+
* TenantId
41+
* CurrentStorageAccountName
42+
* ExtendedProperties
43+
44+
```Cs
45+
// code omitted for brevity
46+
namespace Microsoft.Azure.Commands.Profile.Models
47+
{
48+
public class PSAzureSubscription : IAzureSubscription
49+
{
50+
51+
// code omitted for brevity
52+
53+
public string Id { get; set; }
54+
55+
public string Name { get; set; }
56+
57+
public string State { get; set; }
58+
59+
public string SubscriptionId { get { return Id; } }
60+
61+
public string TenantId
62+
{
63+
get
64+
{
65+
return this.GetTenant();
66+
}
67+
set
68+
{
69+
this.SetTenant(value);
70+
}
71+
}
72+
73+
public string CurrentStorageAccountName
74+
{
75+
get
76+
{
77+
return GetAccountName(CurrentStorageAccount);
78+
}
79+
}
80+
81+
public IDictionary<string, string> ExtendedProperties { get; }
82+
83+
// code omitted for brevity
84+
```
85+
86+
PowerShell uses these properties for the cmdlet table formated output:
87+
88+
```PowerShell
89+
PS C:\> Get-AzureRmSubscription | Format-Table
90+
91+
Id Name State SubscriptionId TenantId CurrentStorageAcc
92+
ountName
93+
-- ---- ----- -------------- -------- -----------------
94+
c9cbd920-c00c-427c-852b-c329e824c3a8 Azure SDK Powershell Test Enabled c9cbd920-c00c-427c-852b-c329e824c3a8 72f988bf-86f1-41af-91ab-7a64d1d63df5
95+
6b085460-5f21-477e-ba44-4cd9fbd030ef Azure SDK Infrastructure Enabled 6b085460-5f21-477e-ba44-4cd9fbd030ef 72f988bf-86f1-41af-91ab-7a64d1d63df5
96+
97+
98+
```
99+
100+
The default table output reveals some issues:
101+
* The selected fields don't fit in a standard window
102+
* The columns are not displayed in order of importance to the customer for doing their work.
103+
* **SubscriptionId** property values duplicates the **Id** property values,
104+
* **CurrentStorageAccountName** property values are empty
105+
* **ExtendedProperties** property values don't fit in the console window and omitted.
106+
107+
# File format.ps1xml.
108+
109+
Powershell allows to configure cmdlets output view with the [format.ps1xml](https://msdn.microsoft.com/en-us/library/gg580992) files.
110+
111+
To provide a better PowerShell Azure cmdlets output experience we worked out a mechanism to quickly generate a [format.ps1xml](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_format.ps1xml?view=powershell-6) file:
112+
113+
114+
1. Mark all the cmdlet output type public properties that should go to the table output with the *Ps1XmlAttribute* attribute.
115+
2. Run the New-FormatPs1Xml cmdlet to generate the format.ps1xml file.
116+
117+
---
118+
We presume that for the [output type](https://github.com/Azure/azure-powershell/blob/preview/documentation/development-docs/azure-powershell-design-guidelines.md#output-type) you created a new class that, for example, wraps a returning .NET SDK type, rather than PSObject.
119+
120+
---
121+
122+
123+
# Ps1XmlAttribute attribute.
124+
125+
The key element of the mechanism is the **Ps1XmlAttribute** attribute located in the [Commands.Common](https://github.com/Azure/azure-powershell/blob/preview/src/Common/Commands.Common/Attributes/Ps1XmlAttribute.cs) project. Below is the attribute definition:
126+
127+
```Cs
128+
namespace Microsoft.WindowsAzure.Commands.Common.Attributes
129+
{
130+
[Flags]
131+
public enum ViewControl
132+
{
133+
None = 0,
134+
Table,
135+
List,
136+
All = Table | List,
137+
}
138+
139+
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
140+
public sealed class Ps1XmlAttribute : Attribute
141+
{
142+
public string Label { get; set; }
143+
144+
public ViewControl Target { get; set; } = ViewControl.Table;
145+
}
146+
}
147+
148+
```
149+
150+
With the attribute you can specify for a public property (or field) a target view (table view is default) and a label.
151+
152+
# Ps1XmlAttribute attribute usage.
153+
154+
Let's say for our example we want to only show these parameters in the output:
155+
* Id
156+
* Name
157+
* State
158+
* TenantId
159+
160+
We just need to add the Ps1Xml attribute to the selected properties:
161+
162+
```Cs
163+
// code omitted for brevity
164+
using Microsoft.WindowsAzure.Commands.Common.Attributes;
165+
166+
namespace Microsoft.Azure.Commands.Profile.Models
167+
{
168+
public class PSAzureSubscription : IAzureSubscription
169+
{
170+
171+
// code omitted for brevity
172+
173+
[Ps1Xml(Label = "Subscription Id", Target = ViewControl.Table)]
174+
public string Id { get; set; }
175+
176+
[Ps1Xml(Label = "Subscription Name", Target = ViewControl.Table)]
177+
public string Name { get; set; }
178+
179+
[Ps1Xml(Label = "State", Target = ViewControl.Table)]
180+
public string State { get; set; }
181+
182+
public string SubscriptionId { get { return Id; } }
183+
184+
[Ps1Xml(Label = "Tenant Id", Target = ViewControl.Table)]
185+
public string TenantId
186+
{
187+
get
188+
{
189+
return this.GetTenant();
190+
}
191+
set
192+
{
193+
this.SetTenant(value);
194+
}
195+
}
196+
197+
public string CurrentStorageAccountName
198+
{
199+
get
200+
{
201+
return GetAccountName(CurrentStorageAccount);
202+
}
203+
}
204+
205+
public IDictionary<string, string> ExtendedProperties { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
206+
207+
// code omitted for brevity
208+
```
209+
* The column order in the output table will be the same as the order of the properties in the class:
210+
```
211+
Id Name State TenantId
212+
== ==== ===== ========
213+
```
214+
* If **Label** is not specified - the property name will be used.
215+
216+
* Since the **Ps1Xml attribute** definition is located in the [Commands.Common](https://github.com/Azure/azure-powershell/tree/preview/src/Common/Commands.Common) project and the Command.Common project is likely referenced from your project - to make the attribute visible - you only need to add ```using Microsoft.WindowsAzure.Commands.Common.Attributes;``` statement.
217+
218+
219+
# How to generate format.ps1xml file.
220+
221+
1. First of all you need to [build](https://github.com/Azure/azure-powershell/blob/preview/documentation/development-docs/azure-powershell-developer-guide.md#building-the-environment) PowerShell Azure:
222+
223+
```Powershell
224+
PS E:\git\azure-powershell> msbuild build.proj /p:SkipHelp=true
225+
```
226+
227+
* After the build is completed you can find build artifacts in the ```.\src\Package\Debug``` folder:
228+
229+
```Powershell
230+
PS E:\git\azure-powershell> ls .\src\Package\Debug\
231+
232+
233+
Directory: E:\git\azure-powershell\src\Package\Debug
234+
235+
236+
Mode LastWriteTime Length Name
237+
---- ------------- ------ ----
238+
d----- 4/25/2018 4:37 PM ResourceManager
239+
d----- 4/25/2018 4:35 PM ServiceManagement
240+
d----- 4/25/2018 4:35 PM Storage
241+
-a---- 4/25/2018 4:31 PM 11384 AzureRM.psd1
242+
-a---- 4/25/2018 4:50 PM 8708 AzureRM.psm1
243+
244+
```
245+
246+
2. Import the **RepoTask cmdlets**:
247+
248+
```PowerShell
249+
PS E:\git\azure-powershell> Import-Module E:\git\azure-powershell\tools\RepoTasks\RepoTasks.Cmdlets\bin\Debug\RepoTasks.Cmdlets.dll
250+
```
251+
3. Run the **New-FormatPs1Xml** cmdlet.
252+
* The cmdlet has one required argument **-ModulePath** - a path to a module manifest (psd1) file. Since in our example we are using the Get-AzureRmSubscription cmdlet from the AzureRM.Profile module we need to specify path to the AzureRm.Profile module manifest which is
253+
```
254+
E:\git\azure-powershell\src\Package\Debug\ResourceManager\AzureResourceManager\AzureRM.Profile\AzureRM.Profile.psd1
255+
```
256+
* Also with the cmdlet we need to use **-OnlyMarkedProperties** switch.
257+
* You may also want to specify an output path for the generated file with the **-OutputPath** argument. If not specified this is current folder.
258+
259+
```
260+
PS E:\git\azure-powershell> New-FormatPs1Xml -ModulePath .\src\Package\Debug\ResourceManager\AzureResourceManager\AzureRM.Profile\AzureRM.Profile.psd1 -OnlyMarkedProperties
261+
262+
E:\git\azure-powershell\Microsoft.Azure.Commands.Profile.generated.format.ps1xml
263+
```
264+
* After a successful run the cmdlet outputs the full path to the generated format.ps1xml file.
265+
266+
# How to test the format.ps1xml file.
267+
268+
**Note:** All the paths used in the example in the section are under **_azure-powershell/src/Package/Debug_**
269+
270+
1. **Copy** the generated format.ps1xml file to the built module folder (this is where your module manifest file psd1 is located). In our example the module folder is
271+
```
272+
E:\git\azure-powershell\src\Package\Debug\ResourceManager\AzureResourceManager\AzureRM.Profile
273+
```
274+
275+
2. Modify your module manifest file.
276+
* In our example the module manifest is AzureRM.Profile.psd1:
277+
```
278+
E:\git\azure-powershell\src\Package\Debug\ResourceManager\AzureResourceManager\AzureRM.Profile\AzureRM.Profile.psd1
279+
```
280+
281+
* In the module manifest file there is a variable called **FormatsToProcess** to reference format.ps1xml files.
282+
If the variable already has a value - **insert** you generated file before the value following by comma (or just replace it).
283+
In our example insert the generated file ```'.\Microsoft.Azure.Commands.Profile.generated.format.ps1xml'``` before the existing one ```'.\Microsoft.Azure.Commands.Profile.format.ps1xml'```:
284+
285+
```Powershell
286+
# script omitted for brevity
287+
288+
# Format files (.ps1xml) to be loaded when importing this module
289+
FormatsToProcess = '.\Microsoft.Azure.Commands.Profile.generated.format.ps1xml', '.\Microsoft.Azure.Commands.Profile.format.ps1xml'
290+
291+
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
292+
NestedModules = @('.\Microsoft.Azure.Commands.Profile.dll')
293+
294+
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
295+
FunctionsToExport = @()
296+
297+
# script omitted for brevity
298+
```
299+
3. Open a **PowerShell window** and **import** your module. In our example it is AzureRm.Profile:
300+
```Powershell
301+
PS C:\> Import-Module E:\git\azure-powershell\src\Package\Debug\ResourceManager\AzureResourceManager\AzureRM.Profile\AzureRM.Profile.psd1
302+
```
303+
304+
4. Try your cmdlet out. In our example it is Get-AuzreRmSubsription:
305+
```Powershell
306+
PS C:\> Get-AzureRmSubscription
307+
308+
Subscription Id Subscription Name State Tenant Id
309+
--------------- ----------------- ----- ---------
310+
c9cbd920-c00c-427c-852b-c329e824c3a8 Azure SDK Powershell Test Enabled 72f988bf-86f1-41af-91ab-7a64d1d63df5
311+
6b085460-5f21-477e-ba44-4cd9fbd030ef Azure SDK Infrastructure Enabled 72f988bf-86f1-41af-91ab-7a64d1d63df5
312+
```
313+
* Note the table output happens without ```| Format-Table``` cmdlet usage.
314+
315+
316+
# How to add the format.ps1xml file to your project.
317+
318+
**Note:** All the paths used in the example in the section are under **_azure-powershell/src/ResourceManager/Profile_**
319+
320+
321+
1. Copy the generated file into your project source folder. In our example this is [src/ResourceManager/Profile/Commands.Profile](https://github.com/Azure/azure-powershell/tree/preview/src/ResourceManager/Profile/Commands.Profile) folder.
322+
323+
2. Reference the generated format.ps1xml file form your project. In our example this is [Commands.Profile.csproj](https://github.com/Azure/azure-powershell/blob/preview/src/ResourceManager/Profile/Commands.Profile/Commands.Profile.csproj) file:
324+
325+
```Xml
326+
<ItemGroup>
327+
<Content Include="Microsoft.Azure.Commands.Profile.generated.format.ps1xml">
328+
<SubType>Designer</SubType>
329+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
330+
</Content>
331+
<Content Include="Microsoft.Azure.Commands.Profile.format.ps1xml">
332+
<SubType>Designer</SubType>
333+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
334+
</Content>
335+
<None Include="..\AzureRM.Profile.psd1">
336+
<Link>AzureRM.Profile.psd1</Link>
337+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
338+
</None>
339+
<Content Include="Microsoft.Azure.Commands.Profile.types.ps1xml">
340+
<SubType>Designer</SubType>
341+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
342+
</Content>
343+
<None Include="MSSharedLibKey.snk" />
344+
<None Include="packages.config" />
345+
<None Include="StartupScripts\*.ps1">
346+
<!-- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> -->
347+
</None>
348+
</ItemGroup>
349+
```
350+
3. Add the generated format.ps1xml file to your source module manifest **FormatsToProcess** variable. In our example this is [src/ResourceManager/Profile/AzureRM.Profile.psd1](https://github.com/Azure/azure-powershell/blob/preview/src/ResourceManager/Profile/AzureRM.Profile.psd1) file:
351+
```Powershell
352+
# script omitted for brevity
353+
354+
# Format files (.ps1xml) to be loaded when importing this module
355+
FormatsToProcess = '.\Microsoft.Azure.Commands.Profile.generated.format.ps1xml', '.\Microsoft.Azure.Commands.Profile.format.ps1xml'
356+
357+
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
358+
NestedModules = @('.\Microsoft.Azure.Commands.Profile.dll')
359+
360+
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
361+
FunctionsToExport = @()
362+
363+
# script omitted for brevity
364+
```
365+

0 commit comments

Comments
 (0)