{"id":5541,"date":"2015-06-15T00:01:00","date_gmt":"2015-06-15T00:01:00","guid":{"rendered":"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/2015\/06\/15\/powershell-custom-type-module\/"},"modified":"2019-02-18T09:47:25","modified_gmt":"2019-02-18T16:47:25","slug":"powershell-custom-type-module","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/scripting\/powershell-custom-type-module\/","title":{"rendered":"PowerShell Custom Type Module"},"content":{"rendered":"<p><b style=\"font-size:12px\">Summary<\/b><span style=\"font-size:12px\">: Create custom types in Windows PowerShell with a free module by Jon Newman.<\/span>\nMicrosoft Scripting Guy, Ed Wilson, is here.\nMicrosoft Scripting Guy, Ed Wilson, is here. Today we have a guest blogger who was with us in 2011, when he wrote <a href=\"http:\/\/blogs.technet.com\/b\/heyscriptingguy\/archive\/2011\/08\/15\/automate-facebook-with-a-free-powershell-module.aspx\" target=\"_blank\">Automate Facebook with a Free PowerShell Module<\/a>.&nbsp;<\/p>\n<p style=\"margin-left:30px\"><span style=\"font-size:12px\">My name is Jon Newman, and I&rsquo;m an old hand at Windows PowerShell. I was on the Windows PowerShell&nbsp;1.0 team, with Bruce, Jim, and all the rest. My primary responsibility was for pipeline execution and error handling. I started at Microsoft in September 1989 (25 years ago!). I wrote management UI and infrastructure for Windows Server for 23 years, then a couple years ago, I switched to service engineering (aka operations).<\/span>\nI created module CType for use in my operations work. I want to restrict the input to certain functions to be the custom objects that are generated by other functions (in my case, the <b>Connection<\/b> object). In addition, I have ~100 SQL queries that generate streams of <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/System.Data.DataRow(v=vs.110).aspx\" target=\"_blank\">System.Data.DataRow<\/a> objects, and I want to manage the output objects and their formatting without having to hand code a similar number of custom C# classes. I have been using and improving CType for over six months, and it has been really useful to me.\nI talked about this idea with Ed at the PowerShell Summit in April 2014. Now I finally have a decent implementation and installation infrastructure. Thanks also to Jason Shirk for reviewing my work and suggesting the &ldquo;CType {}&rdquo; syntax.<\/p>\n<h2>Installing CType<\/h2>\n<p>If you want to cut to the chase, I have made CType available in several ways:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/JonnMsft\/CType\/raw\/master\/CType.msi\" target=\"_blank\">The MSI installer<\/a>. This will install the CType module to your user profile.<\/li>\n<li><a href=\"https:\/\/www.nuget.org\/packages\/CType\/\" target=\"_blank\">The NuGet package<\/a>. NuGet is good for incorporating CType into your Visual Studio project and keeping it up-to-date.<\/li>\n<li><a href=\"https:\/\/chocolatey.org\/packages\/CType\" target=\"_blank\">The Chocolatey package<\/a>. Chocolatey is good for installing CType to many computers and virutal machines via automation and keeping it up-to-date.<\/li>\n<\/ul>\n<p>Run <b>Get-Help about_CType<\/b>, and you are on your way!<\/p>\n<h2>Why create custom types in Windows PowerShell?<\/h2>\n<ol start=\"1\">\n<li>You can specify parameters such that only an instance of your custom type can be used as input. For example, you could define:<\/li>\n<\/ol>\n<p style=\"margin-left:60px\">Function New-MyConnection<\/p>\n<p style=\"margin-left:60px\">{<\/p>\n<p style=\"margin-left:60px\">&nbsp;&nbsp;&nbsp; [OutputType([MyCompany.MyModule.Connection])]<\/p>\n<p style=\"margin-left:60px\">&nbsp;&nbsp;&nbsp; [CmdletBinding()]<\/p>\n<p style=\"margin-left:60px\">&nbsp;&nbsp;&nbsp; param(<\/p>\n<p style=\"margin-left:60px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # parameters<\/p>\n<p style=\"margin-left:60px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; )<\/p>\n<p style=\"margin-left:60px\">&nbsp;&nbsp;&nbsp; # &#8230;<\/p>\n<p style=\"margin-left:60px\">}<\/p>\n<p style=\"margin-left:60px\">function Get-MyData<\/p>\n<p style=\"margin-left:60px\">{<\/p>\n<p style=\"margin-left:60px\">&nbsp;&nbsp;&nbsp; [CmdletBinding()]<\/p>\n<p style=\"margin-left:60px\">&nbsp;&nbsp;&nbsp; param(<\/p>\n<p style=\"margin-left:60px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [MyCompany.MyModule.Connection][parameter(Mandatory=$true)]$Connection<\/p>\n<p style=\"margin-left:60px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # other parameters<\/p>\n<p style=\"margin-left:60px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; )<\/p>\n<p style=\"margin-left:60px\">&nbsp;&nbsp;&nbsp; # &#8230;<\/p>\n<p style=\"margin-left:60px\">}\nIn this example, Get-MyData will only accept an object of the type MyCompany.MyModule.Connection, which presumably was emitted by <b>New-MyConnection<\/b>. Note that only &ldquo;real&rdquo; .NET types will work for this purpose&mdash;it isn&rsquo;t enough to simply add <b>TypeName<\/b> as a decoration with $connection.PSObject.TypeNames.Insert(0,$typename).<\/p>\n<ol start=\"2\">\n<li>You can decorate the type with formatting metadata and other Windows PowerShell type decorations. In this case, it actually would be enough to call $obj.PSObject.TypeNames.Insert(0,$typename).<\/li>\n<li>Windows PowerShell has other issues with the raw <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/System.Data.DataRow(v=vs.110).aspx\" target=\"_blank\">System.Data.DataRow<\/a> class, which I will discuss later.<\/li>\n<\/ol>\n<h3>Why use CType to create custom types?<\/h3>\n<p><a href=\"http:\/\/go.microsoft.com\/fwlink\/p\/?linkid=293943\" target=\"_blank\">Add-Type<\/a> gives you complete flexibility to define your custom types. You could use <strong>Add-Type<\/strong>&nbsp;plus <a href=\"http:\/\/go.microsoft.com\/fwlink\/p\/?linkid=294023\" target=\"_blank\">Update-FormatData<\/a> directly and not bother with CType. However, there are a number of reasons why you might want to use CType:<\/p>\n<ol start=\"1\">\n<li><strong>Add-Type<\/strong> requires you to define your class in CSharp, JScript, or VisualBasic. Scripters may not be comfortable in these programmer languages.<\/li>\n<li>These programming languages make it difficult to create type definitions &ldquo;downstream in the pipeline,&rdquo; which well-constructed Windows PowerShell commands can do. For example, the <b>Add-CType<\/b> parameters include:<\/li>\n<\/ol>\n<p style=\"margin-left:60px\">[string][parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]$PropertyName,<\/p>\n<p style=\"margin-left:60px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [string][parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]$PropertyType,\nCType takes care of translating this into C#. This allows you to generate types dynamically and in a concise manner. I will demonstrate this later.<\/p>\n<ol start=\"3\">\n<li><strong>Update-FormatData<\/strong> requires input in a very cumbersome XML schema. CType also takes care of generating this. The current implementation of CType only supports simple table formats.<\/li>\n<\/ol>\n<h3>What about PowerShell 5.0 class declarations?<\/h3>\n<p>Windows PowerShell 5.0 introduces syntax for class declarations, where Desired State Configuration (DSC) is the first target scenario. These are &ldquo;real&rdquo; .NET classes, and they can be used as function parameter types. (At this writing, I can only comment on the implementation of Windows PowerShell class declarations in the WMF 5.0 November 2014 Tech Preview.)\nYou can define your classes by using Windows PowerShell&nbsp;5.0 class declarations rather than CType if you like; however, there are several advantages to using the CType module:<\/p>\n<ol start=\"1\">\n<li>CType does not require WMF 5.0. Many customers will not install the still-to-be-released (at this writing) official Windows PowerShell 5.0 across all their servers for years, especially customers with dependencies on previous versions of Windows PowerShell (like old versions of System Center).<\/li>\n<li>The class declaration syntax in CType isn&rsquo;t really all that different from CSharp. As with <strong>Add-Typ<\/strong>e, it would be difficult to create Windows PowerShell class declarations &ldquo;downstream in the pipeline.&rdquo;<\/li>\n<li>At this writing, Windows PowerShell class declarations only support short class names. They do not currently support namespaces or class inheritance.<\/li>\n<\/ol>\n<p><b>&nbsp; &nbsp;Note<\/b> It would be possible to implement a variant of CType layered over Windows PowerShell classes rather than over <br \/><b>&nbsp; &nbsp;Add-Type<\/b>. I can&rsquo;t identify any compelling advantages in making that change. There is nothing wrong with Windows <br \/>&nbsp; &nbsp;PowerShell classes&mdash;they just aren&rsquo;t targeted at this scenario.<\/p>\n<h2>Creating a type with function CType<\/h2>\n<p>CType is very easy to use. Simply call CType to add each of your custom types. If you are writing a module, add CType to <b>RequiredModules<\/b> in your .psd1 file, and create the types in the module initialization for your module. Note that a .NET type can only be defined once, so you do not want to call CType for any class more than once. Here is an example:<\/p>\n<p style=\"margin-left:30px\">CType MyCompany.MyNamespace.MyClass {<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; property string Name -Width 20<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; property string HostName -Width 20<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; property DateTime WhenCreated -Width 25<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; property DateTime WhenDeleted -Width 25<\/p>\n<p style=\"margin-left:30px\">}\nWhen the type is defined, you can create an instance by using <b>New-Object<\/b>. You don&rsquo;t have to specify all the properties, and you can change their values at any time.<\/p>\n<p style=\"margin-left:30px\">New-Object &ndash;TypeName MyCompany.MyNamespace.MyClass &ndash;Property @{<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Name = &ldquo;StringValue&rdquo;<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WhenCreated = [DateTime]&#8217;2015.01.01&#8242;<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WhenDeleted = [DateTime]::Now<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }\nOne cool thing about this syntax is that you can use Windows PowerShell structures such as loops, and conditionals, inside the CType declaration. Here is a very simple example:<\/p>\n<p style=\"margin-left:30px\">CType MyCompany.MyNamespace.MyClass {<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; property string Name -Width 20<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; property string HostName -Width 20<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; if ($IncludeTimePropertiesWithThisType)<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; {<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; property DateTime WhenCreated -Width $DateTimeWidth<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; property DateTime WhenDeleted -Width $DateTimeWidth<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; }<\/p>\n<p style=\"margin-left:30px\">}<\/p>\n<h3>Creating a type with Add-CType<\/h3>\n<p>If you want to create types by using classic Windows PowerShell pipelines rather than the &ldquo;little language&rdquo; for function CType, this is an alternate syntax that does exactly the same thing.\n<b>&nbsp; &nbsp;Note&nbsp;<\/b> <b>New-Object<\/b> is only one way to come up with these objects. <b>Select-Object<\/b>, <b>ConvertFrom-CSV<\/b>, or any other way to <br \/>&nbsp; &nbsp;generate objects with properties <b>PropertyType<\/b> and <b>PropertyName<\/b> (and optionally <b>PropertyTableWidth<\/b> and\/or <br \/><b>&nbsp; &nbsp;PropertyKeywords<\/b>) will work.<\/p>\n<p style=\"margin-left:30px\">@(<\/p>\n<p style=\"margin-left:30px\">(New-Object PSObject -Property @{PropertyType=&#8217;string&#8217;;&nbsp;&nbsp; PropertyName=&#8217;Name&#8217;;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PropertyTableWidth=20}),<\/p>\n<p style=\"margin-left:30px\">(New-Object PSObject -Property @{PropertyType=&#8217;string&#8217;;&nbsp;&nbsp; PropertyName=&#8217;HostName&#8217;;&nbsp;&nbsp;&nbsp; PropertyTableWidth=20}),<\/p>\n<p style=\"margin-left:30px\">(New-Object PSObject -Property @{PropertyType=&#8217;DateTime&#8217;; PropertyName=&#8217;WhenCreated&#8217;; PropertyTableWidth=25}),<\/p>\n<p style=\"margin-left:30px\">(New-Object PSObject -Property @{PropertyType=&#8217;DateTime&#8217;; PropertyName=&#8217;WhenDeleted&#8217;;<\/p>\n<p style=\"margin-left:30px\">PropertyTableWidth=25}) `<\/p>\n<p style=\"margin-left:30px\"><span style=\"font-size:12px\">) | Add-CType -TypeName MyCompany.MyNamespace.MyClass<\/span>\nI actually wrote <b>Add-CType<\/b> first, then Jason Shirk suggested the &ldquo;function CType&rdquo; syntax.<\/p>\n<h3>SQL wrapper classes<\/h3>\n<p>If you have created wrappers for SQL queries by using Windows PowerShell, you may have noticed some idiosyncrasies using the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/System.Data.DataRow(v=vs.110).aspx\" target=\"_blank\">System.Data.DataRow<\/a> class in Windows PowerShell, for example:\n<a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.dbnull(v=vs.110).aspx\" target=\"_blank\">System.DBNull<\/a>: If a particular result row does not have a defined value for a particular column, you may see some property values come back as a reference to the singleton instance of type <strong>System.DBNull<\/strong>. This has the advantage of distinguishing between an empty cell and an actual zero or empty string result value. Unfortunately, when Windows PowerShell converts <strong>System.DBNull<\/strong>&nbsp;to Boolean, it comes out as <b>$true<\/b>, so statements like&#8230;<\/p>\n<p style=\"margin-left:30px\">if ($row.Property) {DoThis()}\n&#8230;will actually execute <b>DoThis()<\/b> when the value is <strong>System.DBNull<\/strong>. This is pretty confusing, but it probably can no longer be fixed in Windows PowerShell without breaking backward compatibility. Without the wrapper class, you would have to use a workaround like this:<\/p>\n<p style=\"margin-left:30px\">if (($row.JoinedProperty &ndash;notis [System.DBNull]) &ndash;and $row.JoinedProperty) {DoThis()}\nThe wrapper class takes care of this problem, by turning the <strong>System.DBNull<\/strong>&nbsp;value back to null.<\/p>\n<h4><b>Extra methods<\/b><\/h4>\n<p><b><span>System.Data.DataRow&nbsp;<\/span><\/b><span><\/span>objects contain SQL-specific methods, like <b>BeginEdit()<\/b>, which are probably not relevant to users of your script. The wrapper hides these methods.\nYou create SQL wrapper classes like this:<\/p>\n<p style=\"margin-left:30px\">CType MyCompany.MyNamespace.MyWrapperClass {<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; sqlproperty string Name -Width 20<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; sqlproperty string HostName -Width 20<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; sqlproperty DateTime WhenCreated -Width 25<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; sqlproperty DateTime WhenDeleted -Width 25<\/p>\n<p style=\"margin-left:30px\">}\n<span style=\"font-size:12px\">~or~<\/span><\/p>\n<p style=\"margin-left:30px\">@(<\/p>\n<p style=\"margin-left:30px\">(New-Object PSObject -Property @{PropertyType=&#8217;string&#8217;;&nbsp;&nbsp; PropertyName=&#8217;Name&#8217;;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PropertyTableWidth=20}),<\/p>\n<p style=\"margin-left:30px\">(New-Object PSObject -Property @{PropertyType=&#8217;string&#8217;;&nbsp;&nbsp; PropertyName=&#8217;HostName&#8217;;&nbsp;&nbsp;&nbsp; PropertyTableWidth=20}),<\/p>\n<p style=\"margin-left:30px\">(New-Object PSObject -Property @{PropertyType=&#8217;DateTime&#8217;; PropertyName=&#8217;WhenCreated&#8217;; PropertyTableWidth=25}),<\/p>\n<p style=\"margin-left:30px\">(New-Object PSObject -Property @{PropertyType=&#8217;DateTime&#8217;; PropertyName=&#8217;WhenDeleted&#8217;;<\/p>\n<p style=\"margin-left:30px\">PropertyTableWidth=25}) `<\/p>\n<p style=\"margin-left:30px\"><span style=\"font-size:12px\">) | Add-CType -TypeName MyCompany.MyNamespace.MyWrapperClass -SqlWrapper<\/span>\nYou create instances like this:<\/p>\n<p style=\"margin-left:30px\">$connection = New-Object System.Data.SqlClient.SqlConnection $connectionString<\/p>\n<p style=\"margin-left:30px\">$command = New-Object System.Data.SqlClient.SqlCommand $commandString,$connection<\/p>\n<p style=\"margin-left:30px\">$adapter = New-Object System.Data.SqlClient.SqlDataAdapter $command<\/p>\n<p style=\"margin-left:30px\">$dataset = New-Object System.Data.DataSet<\/p>\n<p style=\"margin-left:30px\">$null = $adapter.Fill($dataSet)<\/p>\n<p style=\"margin-left:30px\">$result = $tables[0].Rows | ConvertTo-CTypeSqlWrapper -ClassName MyCompany.MyNamespace.MyWrapperClass\nIf you already have functions that generate <b>DataRow<\/b> objects, use <b>ConvertTo-CTypeDeclaration<\/b> to create an initial CType wrapper for them. Simply add your type name, add a parent type name (or remove that clause), add widths, and change the property order as desired for formatting, and your declaration is ready!\nThat&rsquo;s it! You can contact me through the following Comment section. I would love to see bugs, suggestions, questions, and new scenarios. Feedback is really important to me&mdash;it&rsquo;s how I decide whether to invest more time in a project like this one. I also have a site on GitHub where you can report issues: <a href=\"https:\/\/github.com\/JonnMsft\/CType\/issues\" target=\"_blank\">Welcome to Issues!<\/a>\nAlso, please contact me if you have any interest in assisting with TeslaFacebookModule. (&ldquo;Start-Car&rdquo; anyone?) And tell Elon Musk to hurry up&hellip;\n~Jon\nThank you, Jon.\nI invite you to follow me on <a href=\"http:\/\/bit.ly\/scriptingguystwitter\" target=\"_blank\">Twitter<\/a> and <a href=\"http:\/\/bit.ly\/scriptingguysfacebook\" target=\"_blank\">Facebook<\/a>. If you have any questions, send email to me at <a href=\"http:\/\/blogs.technet.commailto:scripter@microsoft.com\" target=\"_blank\">scripter@microsoft.com<\/a>, or post your questions on the <a href=\"http:\/\/bit.ly\/scriptingforum\" target=\"_blank\">Official Scripting Guys Forum<\/a>. See you tomorrow. Until then, peace.\n<b>Ed Wilson, Microsoft Scripting Guy<\/b><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Summary: Create custom types in Windows PowerShell with a free module by Jon Newman. Microsoft Scripting Guy, Ed Wilson, is here. Microsoft Scripting Guy, Ed Wilson, is here. Today we have a guest blogger who was with us in 2011, when he wrote Automate Facebook with a Free PowerShell Module.&nbsp; My name is Jon Newman, [&hellip;]<\/p>\n","protected":false},"author":596,"featured_media":87096,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[56,277,597,3,4,598,45],"class_list":["post-5541","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-scripting","tag-guest-blogger","tag-jon-newman","tag-module","tag-scripting-guy","tag-scripting-techniques","tag-types","tag-windows-powershell"],"acf":[],"blog_post_summary":"<p>Summary: Create custom types in Windows PowerShell with a free module by Jon Newman. Microsoft Scripting Guy, Ed Wilson, is here. Microsoft Scripting Guy, Ed Wilson, is here. Today we have a guest blogger who was with us in 2011, when he wrote Automate Facebook with a Free PowerShell Module.&nbsp; My name is Jon Newman, [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/5541","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/users\/596"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/comments?post=5541"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/5541\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media\/87096"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media?parent=5541"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/categories?post=5541"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/tags?post=5541"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}