diff --git a/.gitattributes b/.gitattributes index bdb0cab..45ef6c6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,3 +15,22 @@ *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain + +# Explicitly declare text files we want to always be normalized and converted +# to native line endings on checkout. +*.md text +*.gitattributes text + +# Declare files that will always have CRLF line endings on checkout. +*.ps1 text eol=crlf +*.psm1 text eol=crlf +*.psd1 text eol=crlf +*.psc1 text eol=crlf +*.ps1xml text eol=crlf +*.clixml text eol=crlf +*.xml text eol=crlf +*.txt text eol=crlf + +# Denote all files that are truly binary and should not be mergeable. +*.dll binary +*.exe binary \ No newline at end of file diff --git a/Documentation.md b/Documentation.md new file mode 100644 index 0000000..ac2c585 --- /dev/null +++ b/Documentation.md @@ -0,0 +1,457 @@ +## PSYaml + +PSYaml is a simple PowerShell module that allows you to serialize PowerShell objects to "Yet Another Markup Language" (YAML) documents and deserialize YAML documents to PowerShell objects. It uses [Antoine Aubry's](http://aaubry.net) [excellent YamlDotNet library](http://aaubry.net/pages/yamldotnet.html). + +Prior versions of this module required that you manually procure and move the .NET library into a folder before the module would function. For reference, please reference the [legacy documentation](.\Documentation.adoc). + +## Usage + +```powershell +import-module psyaml +``` +Once the module is in place and working, you can execute code like this +```powershell +[ordered]@{ + Computername = $(Get-wmiobject win32_operatingsystem).csname + OS = $(Get-wmiobject win32_operatingsystem).caption + 'Uptime (hours)' = ((get-date) - ([wmiclass]"").ConvertToDateTime((Get-wmiobject win32_operatingsystem).LastBootUpTime)).Hours + Make = $(get-wmiobject win32_computersystem).model + Manufacturer = $(get-wmiobject win32_computersystem).manufacturer + 'Memory (Gb)' = $(Get-WmiObject win32_computersystem).TotalPhysicalMemory/1GB -as [int] + Processes = (Get-Process).Count + drives = Get-WmiObject Win32_logicaldisk|select DeviceID, description + } | ConvertTo-YAML +``` +to give you a YAML representation of the data that is easy to assimilate. + +```yaml +--- + Computername: 'LTPFACTOR' + OS: 'Microsoft Windows 8.1 Enterprise' + Uptime (hours): 21 + Make: 'Latitude E8770' + Manufacturer: 'Dell Inc.' + Memory (Gb): 8 + Processes: 169 + Drives: + - + DeviceID: 'C:' + description: 'Local Fixed Disk' + - + DeviceID: 'K:' + description: 'Network Connection' + - + DeviceID: 'L:' + description: 'Network Connection' + - + DeviceID: 'M:' + description: 'Network Connection' + - + DeviceID: 'N:' + description: 'Network Connection' + - + DeviceID: 'P:' + description: 'Network Connection' + - + DeviceID: 'S:' + description: 'Network Connection' +``` + +Try it with something like `Format-table` and you'll probably agree that there is a place for rendering hierarchical information in a human-oriented way. + +## YAML and PowerShell + +When you need to use structured data in PowerShell, you have to think of writing it out - serializing it – and reading it into an object– deserialising it. You’ll hear talk of serializing objects, but really, you’re only serializing the data within it, such as properties, lists, collections, dictionaries and so on, rather than the methods. In a compiled language, a serialized object can’t do anything for itself once it has been deserialised and re-serialised. It is just a container for data. PowerShell is unusual in that it can include scripts in objects, as ScriptMethods and ScriptProperties, so it is theoretically possible to transfer both between PowerShell applications, but this is out of the scope of this article. + +You’ve got some choice in PowerShell of how you serialize objects into structured documents, and back again. The two built-in formats are XML and JSON. I’ll be showing you how to get to use a third: YAML. + +## Why YAML? + +You’d need a good reason for not using XML. It is the obvious format for juggling with data. PowerShell allows you to query it and treat it as an object. If you use XML Schemas, you have a very robust system. +The downside of XML is that it is complex, arcane, and the XML documents can’t be easily read or altered by humans. It can take a long time to process. + +JSON is popular because it is so simple that any language can be used to read or write it. The downside is that it doesn’t do much, and has a restricted range of datatypes. You can’t actually specify the data type of a value, for example. It isn’t an intuitive way of laying data out on the page. +YAML is a formalization of the way that we used to lay out taxonomies and forms of structured data before computers. It is easy to understand. When you start doing bulleted lists within lists, it starts to look like YAML. As far as readability goes, here is YAML document + +```yaml +--- +phil: + name: Phil Factor + job: Developer + skills: + - SQL + - python + - perl + - pascal +- derek: + name: Derek DBA + job: DBA + skills: + - TSQL + - fortran + - cobol +``` + + +And here is the same in JSON. + +```json +[ { phil: + { name: 'Phil Factor', + job: 'Developer', + skills: [ 'SQL', 'python', 'perl', 'pascal' ] } }, + { derek: + { name: 'Derek DBA', + job: 'DBA', + skills: [ 'TSQL', 'fortran', 'cobol' ] } } ] +``` + +YAML is officially now a superset of JSON, and so a YAML serializer can usually be persuaded to use the JSON ‘brackety’ style if you prefer, or require, that. The PSYaml module has a function just to convert from the indented dialect of YAML to the 'Brackety' dialect aka JSON. Beware that not everything in YAML will convert to JSON so it is possible to get errors in consequence. + +```powershell +import-module psyaml +Convert-YAMLtoJSON @" +# Employee records +- phil: + name: Phil Factor + job: Developer + skills: + - SQL + - python + - perl + - pascal +- derek: + name: Derek DBA + job: DBA + skills: + - TSQL + - fortran + - cobol +"@ +``` +which will give ... + +```json + +[{"phil": {"name": "Phil Factor", "job": "Developer", "skills": ["SQL", "python", "perl", "pascal"]}}, {"derek": {"name": "Derek DBA", "job": "DBA", "skills": ["TSQL", "fortran", "cobol"]}}] + +``` + +YAML also allows you to specify the data type of its values explicitly. If you wish to ensure that a datatype is read correctly, and Mr and Mrs Null will agree with me on this, you can precede the value with `!!float`, `!!int`, `!!null`, `!!timestamp`, `!!bool`, `!!binary`, `!!Yaml` or `!!str`. These are the most common YAML datatypes that you are likely to across, and any deserializer must cope with them. YAML also allows you to specify a data type that is specific to a particular language or framework, such as geographic coordinates. YAML also contains references, which refer to an existing element in the same document. So, if an element is repeated later in a YAML document, you can simply refer to the element using a short-hand name. + +Another advantage to YAML is that you can specify the type of set or sequence, and whether it is ordered or unordered. It is much more attuned to the rich variety of data that is around. + +I use YAML a great deal for documentation and for configuration settings. I started off by using PowerYAML which is a thin layer around YamlDotNet. Unfortunately, although YamlDotNet is excellent, PowerYAML hadn’t implemented any serialiser, hadn’t implemented data type tags, and couldn’t even auto-detect the data type. As it wasn’t being actively maintained, and was incompatible with the current version of the YamlDotNet library that was doing all the heavy work, I wrote my own module using YamlDotNet directly. + +You merely load the module: +```powershell +import-module psyaml +``` + +and you will have a number of functions that you require. + +We bundle the YamlDotNet library with the module to streamline module usage. + +And we can then create some simple functions + +```powershell +Function YAMLSerialize + { + [CmdletBinding()] + param + ( + [parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] + [object]$PowershellObject + ) +BEGIN { } +PROCESS + {$Serializer = New-Object YamlDotNet.Serialization.Serializer([YamlDotNet.Serialization.SerializationOptions]::emitDefaults) +#None. Roundtrip, DisableAliases, EmitDefaults, JsonCompatible, DefaultToStaticType +$stringBuilder = New-Object System.Text.StringBuilder +$stream = New-Object System.io.StringWriter -ArgumentList $stringBuilder +$Serializer.Serialize($stream,$PowershellObject) #System.IO.TextWriter writer, System.Object graph) +$stream.ToString()} +END {} +} + +Function YAMLDeserialize + + { + [CmdletBinding()] + param + ( + $YamlString + ) +$stringReader = new-object System.IO.StringReader([string]$yamlString) +$Deserializer=New-Object -TypeName YamlDotNet.Serialization.Deserializer -ArgumentList $null, $null, $false +$Deserializer.Deserialize([System.IO.TextReader]$stringReader) +} + +``` +This will give us the basics. Naturally, there is a lot more we can, and will, do; but this will get you started. Of course, this is all done for you in PSYaml and you can access these very functions. + +Now we just want a simple YAML string to test out the plumbing. +```powershell + $YamlString =@" + invoice: !!int 34843 + date : 2001-01-23 + approved: yes + bill-to: &id001 + given : Chris + family : Dumars + address: + lines: | + 458 Walkman Dr. + Suite #292 + city : Royal Oak + state : MI + postal : 48046 + ship-to: *id001 + product: + - sku : BL394D + quantity : 4 + description : Basketball + price : 450.00 + - sku : BL4438H + quantity : 1 + description : Super Hoop + price : 2392.00 + tax : 251.42 + total: 4443.52 + comments: > + Late afternoon is best. + Backup contact is Nancy + Billsmer @ 338-4338. + +"@ +``` + +So let’s create a PowerShell object, and convince ourselves that it can read it in correctly by taking the object it produced, accessing properties from it and then outputting it as JSON. + +```powershell +YAMLSerialize (YAMLDeserialize $yamlString) +``` + +You should get the simple invoice back again. Job done? Well, possibly, but if you need to process the results in PowerShell, you may still hit problems. +You’d expect, from using ConvertFrom-JSON, that this would work: + +```powershell +$MyInvoice=YAMLDeserialize $yamlString +$BillTo=$MyInvoice.'bill-to' + +"Dispatch this to $($BillTo.given) $($BillTo.family) at the address: + $($BillTo.address.lines)$($BillTo.address.city) +$($BillTo.address.state) +($($BillTo.address.postal))" +``` + +But it doesn’t. What is also bad is that in the PowerShell IDE, you haven’t got the intellisense prompt for the object either. You want the equivalent of this to happen with YAML + +```powershell +$JSONInvoice=convertFrom-JSON @' +{ + "invoice": 34843, + "date": "\/Date(980208000000)\/", + "approved": true, + "bill-to": { + "given": "Chris", + "family": "Dumars", + "address": { + "lines": "458 Walkman Dr.\nSuite #292\n", + "city": "Royal Oak", + "state": "MI", + "postal": 48046 + } + }, + "ship-to": "id001", + "product": [ + { + "sku": "BL394D", + "quantity": 4, + "description": "Basketball", + "price": 450.00 + }, + { + "sku": "BL4438H", + "quantity": 1, + "description": "Super Hoop", + "price": 2392.00 + } + ], + "tax": 251.42, + "total": 4443.52, + "comments": "Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338.\n" +} +'@ +$BillTo=$jsonInvoice.'bill-to' + +"Dispatch this to $($BillTo.given) $($BillTo.family) at the address: + $($BillTo.address.lines)$($BillTo.address.city) +$($BillTo.address.state) +($($BillTo.address.postal))" +``` + +...and whatever else in terms of accessing the data via dot notation that you care to try. +The problem is that the YAML deserialiser creates NET objects, which is entirely correct and useful, but it is just more convenient to have PowerShell objects to make them full participants. + +## Refining the Deserializing process. + +Generally speaking, a good library for parsing and emitting data documents does so in two phases. The main work on a string containing XML, YAML, CSV or JSON is to create a representational model. The second phase is to turn that representational model into real data structures that are native to your computer language. + +In the case of YAML, you can have several separate documents in a single YAML string so the parser will return a representational model for every data document within the file:. Each representational model consists of a number of ‘nodes’. All you need to do is to examine each node recursively to create a data object. Each node contains the basics: the style, tag and anchor. The mapping-style of the node is the way it is formatted in the document, The anchor is used where a node references another node to get its value, and a tag tells you what sort of data type it needs, explicitly. This will include ‘omap’, ‘seq’ or ‘map’, where the node contains a list, sequence or a dictionary, or ‘float’, ‘int’, ‘null’, ‘bool’ or ‘str’ if it has a simple value. You can specify your own special data, such as coordinates, table data or whatever you wish. + +A typical YAML library will parse the presentation stream and compose the Representation Graph. The final input process is to construct the native data structures from the YAML representation. The advantage of this is that you can then specify how your special data types are treated in the conversion process. Because YAML is a superset of JSON, you still have to allow untyped values that then have to be checked to see what sort of data it contains. + +Here is a routine that takes as a parameter a representational model and converts it into a PowerShell object. It is easy to check this by converting the resulting object to XML or JSON or even YAML. + +```powershell +function ConvertFrom-YAMLDocument +{ + [CmdletBinding()] + param + ( + [object]$TheNode #you pass in a node that, when you call it, will be the root node. + ) + #initialise variables that are needed for providing the correct powershell data type for a string-based value. + [bool]$ABool = $false; [int]$AnInt = $null; [long]$ALong = $null; [decimal]$adecimal = $null; [single]$ASingle = $null; + [double]$ADouble = $null; [datetime]$ADatetime = '1/1/2000'; + + $TheTypeOfNode = $TheNode.GetType().Name # determine this + Write-Verbose "$TheTypeOfNode = $($theNode)" #just so see what is going on + $Style = $TheNode.Style; $Tag = $TheNode.Tag; $Anchor = $TheNode.Anchor; + Write-Verbose "Tag=$tag, Style=$style, Anchor=$anchor" + if ($TheTypeOfNode -eq 'YamlDocument') #if it is the document, then call recursively with the rrot node + { $TheObject = ConvertFrom-YAMLDocument $TheNode.RootNode } + elseif ($TheTypeOfNode -eq 'YamlMappingNode') #ah mapping nodes + { + $TheObject = [ordered]@{ }; $theNode | + foreach{ $TheObject.($_.Key.Value) = ConvertFrom-YAMLDocument $_.Value; } + } + elseif ($TheTypeOfNode -eq 'YamlScalarNode' -or $TheTypeOfNode -eq 'Object[]') + { + $value = "$($theNode)" + if ($tag -eq $null) + { + $value = switch -Regex ($value) + { + # if it is one of the allowed boolean values + '(?i)\A(?:on|yes)\z' { 'true'; break } #Deal with all the possible YAML boolenas + '(?i)\A(?:off|no)\z' { 'false'; break } + default { $value } + }; + }; + + $TheObject = + if ($tag -ieq 'tag:yaml.org,2002:str') { [string]$Value } #it is specified as a string + elseif ($tag -ieq 'tag:yaml.org,2002:bool') { [bool]$Value } #it is specified as a boolean + elseif ($tag -ieq 'tag:yaml.org,2002:float') { [double]$Value } #it is specified as adouble + elseif ($tag -ieq 'tag:yaml.org,2002:int') { [int]$Value } #it is specified as a int + elseif ($tag -ieq 'tag:yaml.org,2002:null') { $null } #it is specified as a null + elseif ($tag -ieq 'tag:yaml.org,2002:timestamp') {[datetime]$Value} #it is date/timestamp + elseif ($tag -ieq 'tag:yaml.org,2002:binary') {[System.Convert]::FromBase64String($Value)} + elseif ([int]::TryParse($Value, [ref]$AnInt)) { $AnInt } #is it a short integer + elseif ([bool]::TryParse($Value, [ref]$ABool)) { $ABool } #is it a boolean + elseif ([long]::TryParse($Value, [ref]$ALong)) { $ALong } #is it a long integer + elseif ([decimal]::TryParse($Value, [ref]$ADecimal)) { $ADecimal } #is it a decimal + elseif ([single]::TryParse($Value, [ref]$ASingle)) { $ASingle } #is it a single float + elseif ([double]::TryParse($Value, [ref]$ADouble)) { $ADouble } #is it a double float + elseif ([datetime]::TryParse($Value, [ref]$ADatetime)) { $ADatetime } #is it a datetime + else { [string]$Value } + } + elseif ($TheTypeOfNode -eq 'Object[]') #sometimes you just get a raw object, not a node + { $TheObject = $theNode.Value } #so you return its value + elseif ($TheTypeOfNode -eq 'YamlSequenceNode') #in which case you + { $TheObject = @(); $theNode | foreach{ $TheObject += ConvertFrom-YAMLDocument $_ } } + else { Write-Verbose "Unrecognised token $TheTypeOfNode" } + $TheObject +} +``` + +In order to use this, all you need to do is to load the text of the YAML document into a YAML stream. + +```powershell + $stringReader = new-object System.IO.StringReader([string]$yamlString) + $yamlStream = New-Object YamlDotNet.RepresentationModel.YamlStream + $yamlStream.Load([System.IO.TextReader]$stringReader) + ConvertFrom-YAMLDocument ($yamlStream.Documents[0]) +``` + +So there you have it. We now wrap this last code in a function and we have a PowerShell module that we can use whenever we need to parse YAML. I won’t bother to list that here as I’ve put it on GitHub for you. + +I also have added ConvertTo-YAML, because this is handy if you need plenty of control over the way that your PowerShell objects are serialized. Some of these objects are very unwieldy, with a lot of irrelevant information, and if you try serializing them without any sort of filtering, you will accidentally contribute to the Big Data crisis. + +Last but most important, I wanted a way of loading a third party .net library into a module from nuget. I therefore added a function to add the library using add-Type, but which checked to make sure that everything was there first, and load it in the right place if it wasn’t. You can call it explicitly to check that you have the latest version of YamlDotNet. If it breaks something, you just delete the directory that it put the new version in: The module always loads the latest version in the YamlDotNet directory that it can find. + +```powershell +Initialize-PsYAML_Module $True +``` + +## Simple Example of use +Here is a way of producing a YAML result from any SQL expression on a database + +```powershell +import-module psyaml +$SourceTable = 'production.location' +$Sourceinstance = 'YourInstanceName' +$Sourcedatabase = 'Adventureworks' + +$SourceConnectionString = "Data Source=$Sourceinstance;Initial Catalog=$Sourcedatabase;Integrated Security=True" +$sql = "select * FROM $SourceTable" +$result = @() +try +{ + $sourceConnection = New-Object System.Data.SqlClient.SQLConnection($SourceConnectionString) + $sourceConnection.open() + $commandSourceData = New-Object system.Data.SqlClient.SqlCommand($sql, $sourceConnection) + $reader = $commandSourceData.ExecuteReader() + $Counter = $Reader.FieldCount + while ($Reader.Read()) + { + $tuple = @{ } + for ($i = 0; $i -lt $Counter; $i++) + { + $tuple."$($Reader.GetName($i))" = "$(if ($Reader.GetFieldType($i).Name -eq 'DateTime') + { $Reader.GetDateTime($i) } + else { $Reader.GetValue($i) })"; + } + $Result += $tuple + } + YAMLSerialize $result +} +catch +{ + $ex = $_.Exception + Write-Error "whilst opening source $Sourceinstance . $Sourcedatabase . $SourceTable : $ex.Message" +} +finally +{ + $reader.close() +} +``` + +This will give the result (just the first three rows) + +```powershell +- CostRate: 0.0000 + ModifiedDate: 06/01/1998 00:00:00 + Name: Tool Crib + Availability: 0.00 + LocationID: 1 +- CostRate: 0.0000 + ModifiedDate: 06/01/1998 00:00:00 + Name: Sheet Metal Racks + Availability: 0.00 + LocationID: 2 +- CostRate: 0.0000 + ModifiedDate: 06/01/1998 00:00:00 + Name: Paint Shop + Availability: 0.00 + LocationID: 3 +#and so on... +``` +## So what is the point of all this? +Besides the fact that it is an intuitive way of representing data, one of the most important advantages of YAML over JSON is that YAML allows you to [specify your data type](http://www.yaml.org/type). You don't need to in YAML, but it can resolve ambiguity. I've implemented the standard YAML scalar tags of [timestamp](http://www.yaml.org/type/timestamp.html), [binary](http://www.yaml.org/type/binary.html), [str](http://www.yaml.org/type/str.html), [bool](http://www.yaml.org/type/bool.html), [float](http://www.yaml.org/type/float.html), [int](http://www.http://yaml.org/type/int.html) and [null](http://www.yaml.org/type/null.html). If there is no scalar tag, I also autodetect a string to try to get it to the right data type. + +YAML also has a rather crude way of allowing you to represent relational data by means of [node](http://www.yaml.org/spec/1.2/spec.html#node//) Anchors. These A have an '&' prefix. An [alias node](http://www.yaml.org/spec/1.2/spec.html#alias//) can then be used to indicate additional inclusions of the anchored node. It means that you don't have to repeat nodes in a document. You just write it once and then refer to the node by its anchor. + +I find YAML to be very useful. What really convinces me of the power of YAML is to be able to walk the representational model to do special-purpose jobs such as processing hierarchical data to load into SQL. It is at that point that I finally decided that YAML had a lot going for it as a format of data document. \ No newline at end of file diff --git a/Documentation.adoc b/Legacy_Documentation.adoc similarity index 99% rename from Documentation.adoc rename to Legacy_Documentation.adoc index 1c81704..ec60b6a 100644 --- a/Documentation.adoc +++ b/Legacy_Documentation.adoc @@ -501,4 +501,3 @@ YAML also has a rather crude way of allowing you to represent relational data by I find YAML to be very useful. What really convinces me of the power of YAML is to be able to walk the representational model to do special-purpose jobs such as processing hierarchical data to load into SQL. It is at that point that I finally decided that YAML had a lot going for it as a format of data document. - diff --git a/Media/YAML_PS.png b/Media/YAML_PS.png new file mode 100644 index 0000000..320be57 Binary files /dev/null and b/Media/YAML_PS.png differ diff --git a/PSDeploy/PSYaml.PSDeploy.ps1 b/PSDeploy/PSYaml.PSDeploy.ps1 new file mode 100644 index 0000000..199ed54 --- /dev/null +++ b/PSDeploy/PSYaml.PSDeploy.ps1 @@ -0,0 +1,17 @@ +# Specify Module Name +$ModuleName = "PSYaml" + +# Determine our installation path based on where "My Documents" is located +$InstallPath = $env:PSModulePath.split(";") | Where-Object {$_ -match "My Documents" -or $_ -match "Documents"} + +# Specify deploy task +Deploy "Install $ModuleName" { + By Filesystem { + FromSource $ModuleName + To "$($InstallPath)\$($ModuleName)" + WithOptions @{ + # This overrides any existing content + Mirror = $True + } + } +} diff --git a/PSYaml.psd1 b/PSYaml.psd1 deleted file mode 100644 index b09eb9f..0000000 --- a/PSYaml.psd1 +++ /dev/null @@ -1,104 +0,0 @@ -<# - =========================================================================== - Created on: 01-Apr-16 5:20 PM - Created by: Phil Factor - Organization: The Phil Factory - Filename: PSYAML.psd1 - ------------------------------------------------------------------------- - Module Manifest - ------------------------------------------------------------------------- - Module Name: ConvertFromAndToYAML - =========================================================================== -#> - -@{ - -# Script module or binary module file associated with this manifest -ModuleToProcess = 'PSYaml.psm1' - -# Version number of this module. -ModuleVersion = '1.0.0.1' - -# ID used to uniquely identify this module -GUID = 'b8eb2cda-f09b-4c5d-bf3d-f03d212f0a81' - -# Author of this module -Author = 'Phil Factor' - -# Company or vendor of this module -CompanyName = 'Phil Factory' - -# Copyright statement for this module -Copyright = '(c) 2016. All rights reserved.' - -# Description of the functionality provided by this module -Description = 'This module allows you to convert to and from YAML in PowerShell' - -# Minimum version of the Windows PowerShell engine required by this module -PowerShellVersion = '2.0' - -# Name of the Windows PowerShell host required by this module -PowerShellHostName = '' - -# Minimum version of the Windows PowerShell host required by this module -PowerShellHostVersion = '' - -# Minimum version of the .NET Framework required by this module -DotNetFrameworkVersion = '4.0' - -# Minimum version of the common language runtime (CLR) required by this module -CLRVersion = '2.0.50727' - -# Processor architecture (None, X86, Amd64, IA64) required by this module -ProcessorArchitecture = 'None' - -# Modules that must be imported into the global environment prior to importing -# this module -RequiredModules = @() - -# Assemblies that must be loaded prior to importing this module -RequiredAssemblies = @() - -# Script files (.ps1) that are run in the caller's environment prior to -# importing this module -ScriptsToProcess = @() - -# Type files (.ps1xml) to be loaded when importing this module -TypesToProcess = @() - -# Format files (.ps1xml) to be loaded when importing this module -FormatsToProcess = @() - -# Modules to import as nested modules of the module specified in -# ModuleToProcess -NestedModules = @() - -# Functions to export from this module -FunctionsToExport = '*' - -# Cmdlets to export from this module -CmdletsToExport = '*' - -# Variables to export from this module -VariablesToExport = '*' - -# Aliases to export from this module -AliasesToExport = '*' - -# List of all modules packaged with this module -ModuleList = @() - -# List of all files packaged with this module -FileList = @('PsYaml.psd1','PsYaml.psm1') - -# Private data to pass to the module specified in ModuleToProcess -PrivateData = '' - -} - - - - - - - diff --git a/PSYaml.psm1 b/PSYaml.psm1 deleted file mode 100644 index 0b9e5c7..0000000 --- a/PSYaml.psm1 +++ /dev/null @@ -1,445 +0,0 @@ - -<# - .SYNOPSIS - Get latest version of NET assembly for YAMLdotNet, Add the type - - .DESCRIPTION - This basically adds the YamlDotNet assembly. If you haven't got it, it gets it gor you. If you want it to, it updates the assembly to the latest version. - - .PARAMETER CheckForUpdate - Force a check for an update. This is the only reason that this function is exposed. - - .EXAMPLE - PS C:\> Initialize-PsYAML_Module -CheckForUpdate $true #check for update and load - - .NOTES - Additional information about the function. -#> -function Initialize-PsYAML_Module -{ - [CmdletBinding()] - param - ( - [boolean]$CheckForUpdate = $false - ) - - $YAMLDotNetLocation = "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\PSYaml" - $NugetDistribution = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" - push-location #save the current location - Set-Location -Path $YAMLDotNetLocation\YAMLdotNet #set the location in case we need an update - if ($checkForUpdate -or !(test-path "$($YAMLDotNetLocation)\YamlDotNet\YamlDotNet*")) - { - #Is it missing, or are we checking for an update? - if (-not (test-path "$($YAMLDotNetLocation)\nuget.exe")) # is nuget installed? - { - #No nuget! we need to install it. - Invoke-WebRequest $NugetDistribution -OutFile "$($YAMLDotNetLocation)\nuget.exe" - } - Set-Alias nuget "$($YAMLDotNetLocation)\nuget.exe" -Scope Script -Verbose - nuget install yamldotnet #now install or update YAMLDotNet - } - #now get the latest version of YAMLdotNet that we have - $CurrentRelease = Get-ChildItem | where { $_.PSIsContainer } | sort CreationTime -desc | select -f 1 - pop-location - Add-Type -Path "$YAMLDotNetLocation\YAMLDotNet\$CurrentRelease\lib\dotnet\yamldotnet.dll" - -} - -Initialize-PsYAML_Module - - -function Install-PSYamlModule -{ - [CmdletBinding()] - param () - Add-Type -assembly "system.io.compression.filesystem" - # for the unzipping operation - $YAMLDotNetLocation = "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\PSYaml" - # the location of the module - if (!(test-path "$($YAMLDotNetLocation)\YAMLdotNet")) #if the location doesn't exist - { New-Item -ItemType Directory -Force -Path "$($YAMLDotNetLocation)\YAMLdotNet" } #create the location - $client = new-object Net.WebClient #get a webclient to fetch the files - $client.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials - $client.DownloadFile('https://github.com/Phil-Factor/PSYaml/archive/master.zip', "$($YAMLDotNetLocation)PSYAML.zip") - if ((test-path "$($YAMLDotNetLocation)\PSYaml-master")) #delete the existing version if it exists - { Remove-Item "$($YAMLDotNetLocation)\PSYaml-master" -recurse -force } - [io.compression.zipfile]::ExtractToDirectory("$($YAMLDotNetLocation)PSYAML.zip", $YAMLDotNetLocation) - Copy-Item "$YAMLDotNetLocation\PSYaml-master\*.*" $YAMLDotNetLocation #copy it into place -} - - - -function ConvertTo-YAML -{ -<# - .SYNOPSIS - creates a YAML description of the data in the object - .DESCRIPTION - This produces YAML from any object you pass to it. It isn't suitable for the huge objects produced by some of the cmdlets such as Get-Process, but fine for simple objects - .EXAMPLE - $array=@() - $array+=Get-Process wi* | Select-Object Handles,NPM,PM,WS,VM,CPU,Id,ProcessName - ConvertTo-YAML $array - - .PARAMETER Object - the object that you want scripted out - .PARAMETER Depth - The depth that you want your object scripted to - .PARAMETER Nesting Level - internal use only. required for formatting -#> - - [CmdletBinding()] - param ( - [parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] - [AllowNull()] - $inputObject, - [parameter(Position = 1, Mandatory = $false, ValueFromPipeline = $false)] - [int]$depth = 16, - [parameter(Position = 2, Mandatory = $false, ValueFromPipeline = $false)] - [int]$NestingLevel = 0, - [parameter(Position = 3, Mandatory = $false, ValueFromPipeline = $false)] - [int]$XMLAsInnerXML = 0 - ) - - BEGIN { } - PROCESS - { - If ($inputObject -eq $null -and !($inputObject -ne $null)) { $p += 'null'; return $p } # if it is null return null - if ($NestingLevel -eq 0) { '---' } - - $padding = [string]' ' * $NestingLevel # lets just create our left-padding for the block - try - { - $Type = $inputObject.GetType().Name # we start by getting the object's type - if ($Type -ieq 'Object[]') { $Type = "$($inputObject.GetType().BaseType.Name)" } #what it really is - if ($depth -ilt $NestingLevel) { $Type = 'OutOfDepth' } #report the leaves in terms of object type - elseif ($Type -ieq 'XmlDocument' -or $Type -ieq 'XmlElement') - { - if ($XMLAsInnerXML -ne 0) { $Type = 'InnerXML' } - else - { $Type = 'XML' } - } # convert to PS Alias - # prevent these values being identified as an object - if (@('boolean', 'byte', 'byte[]', 'char', 'datetime', 'decimal', 'double', 'float', 'single', 'guid', 'int', 'int32', - 'int16', 'long', 'int64', 'OutOfDepth', 'RuntimeType', 'PSNoteProperty', 'regex', 'sbyte', 'string', - 'timespan', 'uint16', 'uint32', 'uint64', 'uri', 'version', 'void', 'xml', 'datatable', 'Dictionary`2', - 'SqlDataReader', 'datarow', 'ScriptBlock', 'type') -notcontains $type) - { - if ($Type -ieq 'OrderedDictionary') { $Type = 'HashTable' } - elseif ($Type -ieq 'PSCustomObject') { $Type = 'PSObject' } # - elseif ($Type -ieq 'List`1') { $Type = 'Array' } - elseif ($inputObject -is "Array") { $Type = 'Array' } # whatever it thinks it is called - elseif ($inputObject -is "HashTable") { $Type = 'HashTable' } # for our purposes it is a hashtable - elseif (($inputObject | gm -membertype Properties | - Select name | Where name -like 'Keys') -ne $null) { $Type = 'generic' } #use dot notation - elseif (($inputObject | gm -membertype Properties | Select name).count -gt 1) { $Type = 'Object' } - } - write-verbose "$($padding)Type:='$Type', Object type:=$($inputObject.GetType().Name), BaseName:=$($inputObject.GetType().BaseType.Name) " - - switch ($Type) - { - 'ScriptBlock'{ "{$($inputObject.ToString())}" } - 'InnerXML' { "|`r`n" + ($inputObject.OuterXMl.Split("`r`n") | foreach{ "$padding$_`r`n" }) } - 'DateTime' { $inputObject.ToString('s') } # s=SortableDateTimePattern (based on ISO 8601) using local time - 'Byte[]' { - $string = [System.Convert]::ToBase64String($inputObject) - if ($string.Length -gt 100) - { - # right, we have to format it to YAML spec. - '!!binary "\' + "`r`n" # signal that we are going to use the readable Base64 string format - $bits = @(); $length = $string.Length; $IndexIntoString = 0; $wrap = 100 - while ($length -gt $IndexIntoString + $Wrap) - { - $padding + $string.Substring($IndexIntoString, $wrap).Trim() + "`r`n" - $IndexIntoString += $wrap - } - if ($IndexIntoString -lt $length) { $padding + $string.Substring($IndexIntoString).Trim() + "`r`n" } - else { "`r`n" } - } - - else { '!!binary "' + $($string -replace '''', '''''') + '"' } - - } - 'Boolean' { - "$(&{ - if ($inputObject -eq $true) { 'true' } - Else { 'false' } - })" - } - 'string' { - $String = "$inputObject" - if ($string -match '[\r\n]' -or $string.Length -gt 80) - { - # right, we have to format it to YAML spec. - $folded = ">`r`n" # signal that we are going to use the readable 'newlines-folded' format - $string.Split("`n") | foreach { - $length = $_.Length; $IndexIntoString = 0; $wrap = 80 - while ($length -gt $IndexIntoString + $Wrap) - { - $breakpoint = $wrap - $earliest = $_.Substring($IndexIntoString, $wrap).LastIndexOf(' ') - $latest = $_.Substring($IndexIntoString + $wrap).IndexOf(' ') - if (($earliest -eq -1) -or ($latest -eq -1)) { $breakpoint = $wrap } - elseif ($wrap - $earliest -lt ($latest)) { $BreakPoint = $earliest } - else { $BreakPoint = $wrap + $latest } - if (($wrap - $earliest) + $latest -gt 30) { $BreakPoint = $wrap } # in case it is a string without spaces - $folded += $padding + $_.Substring($IndexIntoString, $BreakPoint).Trim() + "`r`n" - $IndexIntoString += $BreakPoint - } - if ($IndexIntoString -lt $length) { $folded += $padding + $_.Substring($IndexIntoString).Trim() + "`r`n`r`n" } - else { $folded += "`r`n`r`n" } - } - $folded - } - else { "'$($string -replace '''', '''''')'" } - } - 'Char' { "([int]$inputObject)" } - { - @('byte', 'decimal', 'double', 'float', 'single', 'int', 'int32', 'int16', ` - 'long', 'int64', 'sbyte', 'uint16', 'uint32', 'uint64') -contains $_ - } - { "$inputObject" } # rendered as is without single quotes - 'PSNoteProperty' { "$(ConvertTo-YAML -inputObject $inputObject.Value -depth $depth -NestingLevel ($NestingLevel + 1))" } - 'Array' { "$($inputObject | ForEach { "`r`n$padding- $(ConvertTo-YAML -inputObject $_ -depth $depth -NestingLevel ($NestingLevel + 1))" })" } - 'HashTable'{ - ("$($inputObject.GetEnumerator() | ForEach { - "`r`n$padding $($_.Name): " + - (ConvertTo-YAML -inputObject $_.Value -depth $depth -NestingLevel ($NestingLevel + 1)) - })") - } - 'Dictionary`2'{ - ("$($inputObject.GetEnumerator() | ForEach { - "`r`n$padding $($_.Key): " + - (ConvertTo-YAML -inputObject $_.Value -depth $depth -NestingLevel ($NestingLevel + 1)) - })") - } - 'PSObject' { ("$($inputObject.PSObject.Properties | ForEach { "`r`n$padding $($_.Name): " + (ConvertTo-YAML -inputObject $_ -depth $depth -NestingLevel ($NestingLevel + 1)) })") } - 'generic' { "$($inputObject.Keys | ForEach { "`r`n$padding $($_): $(ConvertTo-YAML -inputObject $inputObject.$_ -depth $depth -NestingLevel ($NestingLevel + 1))" })" } - 'Object' { ("$($inputObject | Get-Member -membertype properties | Select-Object name | ForEach { "`r`n$padding $($_.name): $(ConvertTo-YAML -inputObject $inputObject.$($_.name) -depth $NestingLevel -NestingLevel ($NestingLevel + 1))" })") } - 'XML' { ("$($inputObject | Get-Member -membertype properties | where-object { @('xml', 'schema') -notcontains $_.name } | Select-Object name | ForEach { "`r`n$padding $($_.name): $(ConvertTo-YAML -inputObject $inputObject.$($_.name) -depth $depth -NestingLevel ($NestingLevel + 1))" })") } - 'DataRow' { ("$($inputObject | Get-Member -membertype properties | Select-Object name | ForEach { "`r`n$padding $($_.name): $(ConvertTo-YAML -inputObject $inputObject.$($_.name) -depth $depth -NestingLevel ($NestingLevel + 1))" })") } - # 'SqlDataReader'{$all = $inputObject.FieldCount; while ($inputObject.Read()) {for ($i = 0; $i -lt $all; $i++) {"`r`n$padding $($Reader.GetName($i)): $(ConvertTo-YAML -inputObject $($Reader.GetValue($i)) -depth $depth -NestingLevel ($NestingLevel+1))"}} - default { "'$inputObject'" } - } - } - catch - { - write-error "Error'$($_)' in script $($_.InvocationInfo.ScriptName) $($_.InvocationInfo.Line.Trim()) (line $($_.InvocationInfo.ScriptLineNumber)) char $($_.InvocationInfo.OffsetInLine) executing $($_.InvocationInfo.MyCommand) on $type object '$($inputObject)' Class: $($inputObject.GetType().Name) BaseClass: $($inputObject.GetType().BaseType.Name) " - } - finally { } - } - - END { } -} - - - - - - <# - .SYNOPSIS - Converts from a YAMLdotNet Document - - .DESCRIPTION - A detailed description of the ConvertFrom-YAMLDocument function. - - .PARAMETER TheNode - A description of the TheNode parameter. - - .EXAMPLE - PS C:\> ConvertFrom-YAMLDocument -TheNode $value1 - - .EXAMPLE - $result=ConvertFrom-YAMLDocument $document -verbose - $result|Convertto-YAML - - .NOTES - =========================================================================== - Created on: 22-Feb-16 7:57 PM - Created by: Phil Factor - Organization: Phil Factory - Filename: - =========================================================================== - - #> -<# - .SYNOPSIS - Converts from a YAMLdotNet Document - - .DESCRIPTION - A detailed description of the ConvertFrom-YAMLDocument function. - - .PARAMETER TheNode - A description of the TheNode parameter. - - .EXAMPLE - PS C:\> ConvertFrom-YAMLDocument -TheNode $value1 - - .EXAMPLE - $result=ConvertFrom-YAMLDocument $document -verbose - $result|Convertto-YAML - - .NOTES - =========================================================================== - Created on: 22-Feb-16 7:57 PM - Created by: Phil Factor - Organization: Phil Factory - Filename: - =========================================================================== - -#> -function ConvertFrom-YAMLDocument -{ - [CmdletBinding()] - param - ( - [object]$TheNode #you pass in a node that, when you call it, will be the root node. - ) - #initialise variables that are needed for providing the correct powershell data type for a string-based value. - [bool]$ABool = $false; [int]$AnInt = $null; [long]$ALong = $null; [decimal]$adecimal = $null; [single]$ASingle = $null; - [double]$ADouble = $null; [datetime]$ADatetime = '1/1/2000'; - - $TheTypeOfNode = $TheNode.GetType().Name # determine this - Write-Verbose "$TheTypeOfNode = $($theNode)" #just so see what is going on - $Style = $TheNode.Style; $Tag = $TheNode.Tag; $Anchor = $TheNode.Anchor; - Write-Verbose "Tag=$tag, Style=$style, Anchor=$anchor" - if ($TheTypeOfNode -eq 'YamlDocument') #if it is the document, then call recursively with the rrot node - { $TheObject = ConvertFrom-YAMLDocument $TheNode.RootNode } - elseif ($TheTypeOfNode -eq 'YamlMappingNode') #ah mapping nodes - { - $TheObject = [ordered]@{ }; $theNode | - foreach{ $TheObject.($_.Key.Value) = ConvertFrom-YAMLDocument $_.Value; } - } - elseif ($TheTypeOfNode -eq 'YamlScalarNode' -or $TheTypeOfNode -eq 'Object[]') - { - $value = "$($theNode)" - if ($tag -eq $null) - { - $value = switch -Regex ($value) - { - # if it is one of the allowed boolean values - '(?i)\A(?:on|yes)\z' { 'true'; break } #Deal with all the possible YAML boolenas - '(?i)\A(?:off|no)\z' { 'false'; break } - default { $value } - }; - }; - - $TheObject = - if ($tag -ieq 'tag:yaml.org,2002:str') { [string]$Value } #it is specified as a string - elseif ($tag -ieq 'tag:yaml.org,2002:bool') { [bool]$Value } #it is specified as a boolean - elseif ($tag -ieq 'tag:yaml.org,2002:float') { [double]$Value } #it is specified as adouble - elseif ($tag -ieq 'tag:yaml.org,2002:int') { [int]$Value } #it is specified as a int - elseif ($tag -ieq 'tag:yaml.org,2002:null') { $null } #it is specified as a null - elseif ($tag -ieq 'tag:yaml.org,2002:timestamp') {[datetime]$Value} #it is date/timestamp - elseif ($tag -ieq 'tag:yaml.org,2002:binary') {[System.Convert]::FromBase64String($Value)} - elseif ([int]::TryParse($Value, [ref]$AnInt)) { $AnInt } #is it a short integer - elseif ([bool]::TryParse($Value, [ref]$ABool)) { $ABool } #is it a boolean - elseif ([long]::TryParse($Value, [ref]$ALong)) { $ALong } #is it a long integer - elseif ([decimal]::TryParse($Value, [ref]$ADecimal)) { $ADecimal } #is it a decimal - elseif ([single]::TryParse($Value, [ref]$ASingle)) { $ASingle } #is it a single float - elseif ([double]::TryParse($Value, [ref]$ADouble)) { $ADouble } #is it a double float - elseif ([datetime]::TryParse($Value, [ref]$ADatetime)) { $ADatetime } #is it a datetime - else { [string]$Value } - } - elseif ($TheTypeOfNode -eq 'Object[]') #sometimes you just get a raw object, not a node - { $TheObject = $theNode.Value } #so you return its value - elseif ($TheTypeOfNode -eq 'YamlSequenceNode') #in which case you - { $TheObject = @(); $theNode | foreach{ $TheObject += ConvertFrom-YAMLDocument $_ } } - else { Write-Verbose "Unrecognised token $TheTypeOfNode" } - $TheObject -} - -function ConvertFrom-YAML - { - [CmdletBinding()] - param - ( - [parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] - $YamlString - ) -BEGIN { } -PROCESS - {$stringReader = new-object System.IO.StringReader([string]$yamlString) - $yamlStream = New-Object YamlDotNet.RepresentationModel.YamlStream - $yamlStream.Load([System.IO.TextReader]$stringReader) - ConvertFrom-YAMLDocument ($yamlStream.Documents[0])} -END {} -} - - -Function JSONSerialize - { - [CmdletBinding()] - param - ( - [parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] - [object]$PowershellObject - ) -BEGIN { } -PROCESS - {$Serializer = New-Object YamlDotNet.Serialization.Serializer([YamlDotNet.Serialization.SerializationOptions]::JsonCompatible) -#None. Roundtrip, DisableAliases, EmitDefaults, JsonCompatible, DefaultToStaticType -$stringBuilder = New-Object System.Text.StringBuilder -$stream = New-Object System.io.StringWriter -ArgumentList $stringBuilder -$Serializer.Serialize($stream,$PowershellObject) #System.IO.TextWriter writer, System.Object graph) -$stream.ToString()} -END {} -} - -Function YAMLSerialize - { - [CmdletBinding()] - param - ( - [parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] - [object]$PowershellObject - ) -BEGIN { } -PROCESS - {$Serializer = New-Object YamlDotNet.Serialization.Serializer([YamlDotNet.Serialization.SerializationOptions]::emitDefaults) -#None. Roundtrip, DisableAliases, EmitDefaults, JsonCompatible, DefaultToStaticType -$stringBuilder = New-Object System.Text.StringBuilder -$stream = New-Object System.io.StringWriter -ArgumentList $stringBuilder -$Serializer.Serialize($stream,$PowershellObject) #System.IO.TextWriter writer, System.Object graph) -$stream.ToString()} -END {} -} - -Function YAMLDeserialize - - { - [CmdletBinding()] - param - ( - $YamlString - ) -$stringReader = new-object System.IO.StringReader([string]$yamlString) -$Deserializer=New-Object -TypeName YamlDotNet.Serialization.Deserializer -ArgumentList $null, $null, $false -$Deserializer.Deserialize([System.IO.TextReader]$stringReader) -} - - - -Function Convert-YAMLtoJSON - { - param - ( - [parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] - $YamlString - ) -BEGIN { } -PROCESS - {$stringReader = new-object System.IO.StringReader([string]$yamlString) - $Deserializer = New-Object -TypeName YamlDotNet.Serialization.Deserializer -ArgumentList $null, $null, $false - $netObject = $Deserializer.Deserialize([System.IO.TextReader]$stringReader) - $Serializer = New-Object YamlDotNet.Serialization.Serializer([YamlDotNet.Serialization.SerializationOptions]::JsonCompatible) - #None. Roundtrip, DisableAliases, EmitDefaults, JsonCompatible, DefaultToStaticType - $stringBuilder = New-Object System.Text.StringBuilder - $stream = New-Object System.io.StringWriter -ArgumentList $stringBuilder - $Serializer.Serialize($stream, $netObject) # - $stream.ToString()} -END {} -} - -Export-ModuleMember ConvertTo-YAML, ConvertFrom-YAMLDocument, ConvertFrom-YAML, YAMLDeserialize, YAMLSerialize, JSONSerialize, Convert-YAMLtoJSON diff --git a/PSYaml/PSYaml.psd1 b/PSYaml/PSYaml.psd1 new file mode 100644 index 0000000..9399e7f --- /dev/null +++ b/PSYaml/PSYaml.psd1 @@ -0,0 +1,127 @@ +# +# Module manifest for module 'PSYaml' +# +# Generated by: Phil-Factor, Pezhore +# +# Generated on: 6/2/2017 +# + +@{ + +# Script module or binary module file associated with this manifest. +RootModule = 'PSYaml.psm1' + +# Version number of this module. +ModuleVersion = '1.0.2' + +# Supported PSEditions +# CompatiblePSEditions = @() + +# ID used to uniquely identify this module +GUID = '56352cea-dba5-4103-b55e-1e33d6cf5806' + +# Author of this module +Author = 'Phil-Factor, Pezhore' + +# Company or vendor of this module +CompanyName = '' + +# Copyright statement for this module +Copyright = '(c) 2017 Phil-Factor, Pezhore. All rights reserved.' + +# Description of the functionality provided by this module +Description = 'PowerShell module used to intrepret Yaml formatted strings' + +# Minimum version of the Windows PowerShell engine required by this module +PowerShellVersion = '2.0' + +# Name of the Windows PowerShell host required by this module +# PowerShellHostName = '' + +# Minimum version of the Windows PowerShell host required by this module +# PowerShellHostVersion = '' + +# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +DotNetFrameworkVersion = '4.0' + +# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +CLRVersion = '2.0.50727' + +# Processor architecture (None, X86, Amd64) required by this module +# ProcessorArchitecture = '' + +# Modules that must be imported into the global environment prior to importing this module +# RequiredModules = @() + +# Assemblies that must be loaded prior to importing this module +RequiredAssemblies = @("lib\YamlDotNet.dll") + +# Script files (.ps1) that are run in the caller's environment prior to importing this module. +# ScriptsToProcess = @() + +# Type files (.ps1xml) to be loaded when importing this module +# TypesToProcess = @() + +# Format files (.ps1xml) to be loaded when importing this module +# FormatsToProcess = @() + +# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess +# NestedModules = @() + +# 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. +FunctionsToExport = @('ConvertFrom-Yaml','ConvertTo-Yaml','Convert-YamlToJson') + +# Cmdlets 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 cmdlets to export. +CmdletsToExport = @() + +# Variables to export from this module +# VariablesToExport = @() + +# Aliases 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 aliases to export. +AliasesToExport = @() + +# DSC resources to export from this module +# DscResourcesToExport = @() + +# List of all modules packaged with this module +# ModuleList = @() + +# List of all files packaged with this module +FileList = @( + '.\PSYaml.psd1','.\PSYaml.psm1','.\en-US\about_PSYaml.help.txt','.\lib\YamlDotNet.dll','.\Private\ConvertFrom-YAMLDocument.ps1', + '.\Private\JSONSerialize.ps1','.\Private\YAMLDeserialize.ps1','.\Private\YAMLSerialize.ps1','.\Public\Convert-YamlToJson.ps1', + '.\Public\ConvertFrom-Yaml.ps1','.\Public\ConvertTo-Yaml.ps1' + ) + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + Tags = @('YAML','PSYaml') + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + ProjectUri = 'https://github.com/pezhore/PSYaml' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + ReleaseNotes = 'Made changes to adhere to ScriptAnalyzer rules, split out varoius if/else statements for readability.' + + } # End of PSData hashtable + +} # End of PrivateData hashtable + +# HelpInfo URI of this module +# HelpInfoURI = '' + +# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. +# DefaultCommandPrefix = '' + +} + diff --git a/PSYaml/PSYaml.psm1 b/PSYaml/PSYaml.psm1 new file mode 100644 index 0000000..99882c5 --- /dev/null +++ b/PSYaml/PSYaml.psm1 @@ -0,0 +1,24 @@ +#Get public and private function definition files. +$Public = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue ) +$Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue ) + +#Dot source the files +Foreach ($import in @($Public + $Private)) +{ + Try + { + . $import.fullname + } + Catch + { + Write-Error -Message "Failed to import function $($import.fullname): $_" + } +} + +# Here I might... +# Read in or create an initial config file and variable +# Export Public functions ($Public.BaseName) for WIP modules +# Set variables visible to the module and its functions only +# Export aliases to functions + +Export-ModuleMember -Function $Public.Basename \ No newline at end of file diff --git a/PSYaml/Private/ConvertFrom-YAMLDocument.ps1 b/PSYaml/Private/ConvertFrom-YAMLDocument.ps1 new file mode 100644 index 0000000..9f3cb4f --- /dev/null +++ b/PSYaml/Private/ConvertFrom-YAMLDocument.ps1 @@ -0,0 +1,117 @@ +function ConvertFrom-YAMLDocument +{ +<# + .SYNOPSIS + Converts from a YAMLdotNet Document + + .DESCRIPTION + A detailed description of the ConvertFrom-YAMLDocument function. + + .PARAMETER TheNode + A description of the TheNode parameter. + + .EXAMPLE + PS C:\> ConvertFrom-YAMLDocument -TheNode $value1 + + .EXAMPLE + $result=ConvertFrom-YAMLDocument $document -verbose + $result|Convertto-YAML + + .NOTES + =========================================================================== + Created on: 22-Feb-16 7:57 PM + Created by: Phil Factor + Organization: Phil Factory + Filename: + =========================================================================== + +#> + [CmdletBinding()] + param ( + [object]$TheNode #you pass in a node that, when you call it, will be the root node. + ) + + #initialise variables that are needed for providing the correct powershell data type for a string-based value. + [bool]$ABool = $false + [int]$AnInt = $null + [long]$ALong = $null + [decimal]$adecimal = $null + [single]$ASingle = $null + [double]$ADouble = $null + [datetime]$ADatetime = '1/1/2000' + + $TheTypeOfNode = $TheNode.GetType().Name # determine this + + Write-Verbose "$TheTypeOfNode = $($theNode)" #just so see what is going on + $Style = $TheNode.Style + $Tag = $TheNode.Tag + $Anchor = $TheNode.Anchor + + Write-Verbose "Tag=$tag, Style=$style, Anchor=$anchor" + + #if it is the document, then call recursively with the rrot node + if ($TheTypeOfNode -eq 'YamlDocument') + { + $TheObject = ConvertFrom-YAMLDocument $TheNode.RootNode + } + elseif ($TheTypeOfNode -eq 'YamlMappingNode') #ah mapping nodes + { + $TheObject = [ordered]@{ } + $theNode | ForEach-Object{ + $TheObject.($_.Key.Value) = ConvertFrom-YAMLDocument $_.Value + } + } + elseif ($TheTypeOfNode -eq 'YamlScalarNode' -or $TheTypeOfNode -eq 'Object[]') + { + $value = "$($theNode)" + if (! $tag) + { + $value = switch -Regex ($value) + { + # if it is one of the allowed boolean values + '(?i)\A(?:on|yes)\z' { 'true' + break + } #Deal with all the possible YAML boolenas + '(?i)\A(?:off|no)\z' { 'false' + break + } + default { $value } + } + + } + + + $TheObject = if ($tag -ieq 'tag:yaml.org,2002:str') { [string]$Value } #it is specified as a string + elseif ($tag -ieq 'tag:yaml.org,2002:bool') { [bool]$Value } #it is specified as a boolean + elseif ($tag -ieq 'tag:yaml.org,2002:float') { [double]$Value } #it is specified as adouble + elseif ($tag -ieq 'tag:yaml.org,2002:int') { [int]$Value } #it is specified as a int + elseif ($tag -ieq 'tag:yaml.org,2002:null') { $null } #it is specified as a null + elseif ($tag -ieq 'tag:yaml.org,2002:timestamp') {[datetime]$Value} #it is date/timestamp + elseif ($tag -ieq 'tag:yaml.org,2002:binary') {[System.Convert]::FromBase64String($Value)} + elseif ([int]::TryParse($Value, [ref]$AnInt)) { $AnInt } #is it a short integer + elseif ([bool]::TryParse($Value, [ref]$ABool)) { $ABool } #is it a boolean + elseif ([long]::TryParse($Value, [ref]$ALong)) { $ALong } #is it a long integer + elseif ([decimal]::TryParse($Value, [ref]$ADecimal)) { $ADecimal } #is it a decimal + elseif ([single]::TryParse($Value, [ref]$ASingle)) { $ASingle } #is it a single float + elseif ([double]::TryParse($Value, [ref]$ADouble)) { $ADouble } #is it a double float + elseif ([datetime]::TryParse($Value, [ref]$ADatetime)) { $ADatetime } #is it a datetime + else { [string]$Value } + } + elseif ($TheTypeOfNode -eq 'Object[]') #sometimes you just get a raw object, not a node + { + $TheObject = $theNode.Value #so you return its value + } + elseif ($TheTypeOfNode -eq 'YamlSequenceNode') #in which case you + { + $TheObject = @() + $theNode | ForEach-Object{ + $TheObject += ConvertFrom-YAMLDocument $_ + } + } + else + { + Write-Verbose "Unrecognised token $TheTypeOfNode" + } + + Return $TheObject +} diff --git a/PSYaml/Private/JSONSerialize.ps1 b/PSYaml/Private/JSONSerialize.ps1 new file mode 100644 index 0000000..39fa1ce --- /dev/null +++ b/PSYaml/Private/JSONSerialize.ps1 @@ -0,0 +1,18 @@ +Function JSONSerialize + { + [CmdletBinding()] + param + ( + [parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] + [object]$PowershellObject + ) +BEGIN { } +PROCESS + {$Serializer = New-Object YamlDotNet.Serialization.Serializer([YamlDotNet.Serialization.SerializationOptions]::JsonCompatible) +#None. Roundtrip, DisableAliases, EmitDefaults, JsonCompatible, DefaultToStaticType +$stringBuilder = New-Object System.Text.StringBuilder +$stream = New-Object System.io.StringWriter -ArgumentList $stringBuilder +$Serializer.Serialize($stream,$PowershellObject) #System.IO.TextWriter writer, System.Object graph) +$stream.ToString()} +END {} +} \ No newline at end of file diff --git a/PSYaml/Private/YAMLDeserialize.ps1 b/PSYaml/Private/YAMLDeserialize.ps1 new file mode 100644 index 0000000..1d65d36 --- /dev/null +++ b/PSYaml/Private/YAMLDeserialize.ps1 @@ -0,0 +1,12 @@ +Function YAMLDeserialize + + { + [CmdletBinding()] + param + ( + $YamlString + ) +$stringReader = new-object System.IO.StringReader([string]$yamlString) +$Deserializer=New-Object -TypeName YamlDotNet.Serialization.Deserializer -ArgumentList $null, $null, $false +$Deserializer.Deserialize([System.IO.TextReader]$stringReader) +} \ No newline at end of file diff --git a/PSYaml/Private/YAMLSerialize.ps1 b/PSYaml/Private/YAMLSerialize.ps1 new file mode 100644 index 0000000..7a6fe24 --- /dev/null +++ b/PSYaml/Private/YAMLSerialize.ps1 @@ -0,0 +1,18 @@ +Function YAMLSerialize + { + [CmdletBinding()] + param + ( + [parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] + [object]$PowershellObject + ) +BEGIN { } +PROCESS + {$Serializer = New-Object YamlDotNet.Serialization.Serializer([YamlDotNet.Serialization.SerializationOptions]::emitDefaults) +#None. Roundtrip, DisableAliases, EmitDefaults, JsonCompatible, DefaultToStaticType +$stringBuilder = New-Object System.Text.StringBuilder +$stream = New-Object System.io.StringWriter -ArgumentList $stringBuilder +$Serializer.Serialize($stream,$PowershellObject) #System.IO.TextWriter writer, System.Object graph) +$stream.ToString()} +END {} +} \ No newline at end of file diff --git a/PSYaml/Public/Convert-YamlToJson.ps1 b/PSYaml/Public/Convert-YamlToJson.ps1 new file mode 100644 index 0000000..35aa1b1 --- /dev/null +++ b/PSYaml/Public/Convert-YamlToJson.ps1 @@ -0,0 +1,20 @@ +Function Convert-YamlToJson + { + param + ( + [parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] + $YamlString + ) +BEGIN { } +PROCESS + {$stringReader = new-object System.IO.StringReader([string]$yamlString) + $Deserializer = New-Object -TypeName YamlDotNet.Serialization.Deserializer -ArgumentList $null, $null, $false + $netObject = $Deserializer.Deserialize([System.IO.TextReader]$stringReader) + $Serializer = New-Object YamlDotNet.Serialization.Serializer([YamlDotNet.Serialization.SerializationOptions]::JsonCompatible) + #None. Roundtrip, DisableAliases, EmitDefaults, JsonCompatible, DefaultToStaticType + $stringBuilder = New-Object System.Text.StringBuilder + $stream = New-Object System.io.StringWriter -ArgumentList $stringBuilder + $Serializer.Serialize($stream, $netObject) # + $stream.ToString()} +END {} +} \ No newline at end of file diff --git a/PSYaml/Public/ConvertFrom-Yaml.ps1 b/PSYaml/Public/ConvertFrom-Yaml.ps1 new file mode 100644 index 0000000..7af1436 --- /dev/null +++ b/PSYaml/Public/ConvertFrom-Yaml.ps1 @@ -0,0 +1,16 @@ +function ConvertFrom-Yaml + { + [CmdletBinding()] + param + ( + [parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] + $YamlString + ) +BEGIN { } +PROCESS + {$stringReader = new-object System.IO.StringReader([string]$yamlString) + $yamlStream = New-Object YamlDotNet.RepresentationModel.YamlStream + $yamlStream.Load([System.IO.TextReader]$stringReader) + ConvertFrom-YAMLDocument ($yamlStream.Documents[0])} +END {} +} \ No newline at end of file diff --git a/PSYaml/Public/ConvertTo-Yaml.ps1 b/PSYaml/Public/ConvertTo-Yaml.ps1 new file mode 100644 index 0000000..295fdd0 --- /dev/null +++ b/PSYaml/Public/ConvertTo-Yaml.ps1 @@ -0,0 +1,247 @@ +function ConvertTo-Yaml +{ +<# + .SYNOPSIS + creates a YAML description of the data in the object + .DESCRIPTION + This produces YAML from any object you pass to it. It isn't suitable for the huge objects produced by some of the cmdlets such as Get-Process, but fine for simple objects + .EXAMPLE + $array=@() + $array+=Get-Process wi* | Select-Object-Object Handles,NPM,PM,WS,VM,CPU,Id,ProcessName + ConvertTo-YAML $array + + .PARAMETER Object + the object that you want scripted out + .PARAMETER Depth + The depth that you want your object scripted to + .PARAMETER Nesting Level + internal use only. required for formatting +#> + [OutputType('System.String')] + + [CmdletBinding()] + param ( + [parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] + [AllowNull()] + $inputObject, + [parameter(Position = 1, Mandatory = $false, ValueFromPipeline = $false)] + [int]$depth = 16, + [parameter(Position = 2, Mandatory = $false, ValueFromPipeline = $false)] + [int]$NestingLevel = 0, + [parameter(Position = 3, Mandatory = $false, ValueFromPipeline = $false)] + [int]$XMLAsInnerXML = 0 + ) + + BEGIN { } + PROCESS + { + # if it is null return null + If ( !($inputObject) ) + { + $p += 'null' + return $p + } + + if ($NestingLevel -eq 0) { '---' } + + $padding = [string]' ' * $NestingLevel # lets just create our left-padding for the block + try + { + $Type = $inputObject.GetType().Name # we start by getting the object's type + if ($Type -ieq 'Object[]') + { + #what it really is + $Type = "$($inputObject.GetType().BaseType.Name)" + } + + #report the leaves in terms of object type + if ($depth -ilt $NestingLevel) + { + $Type = 'OutOfDepth' + } + elseif ($Type -ieq 'XmlDocument' -or $Type -ieq 'XmlElement') + { + if ($XMLAsInnerXML -ne 0) + { + $Type = 'InnerXML' + } + else + { + $Type = 'XML' + } + } # convert to PS Alias + + # prevent these values being identified as an object + if (@('boolean', 'byte', 'byte[]', 'char', 'datetime', 'decimal', 'double', 'float', 'single', 'guid', 'int', 'int32', + 'int16', 'long', 'int64', 'OutOfDepth', 'RuntimeType', 'PSNoteProperty', 'regex', 'sbyte', 'string', + 'timespan', 'uint16', 'uint32', 'uint64', 'uri', 'version', 'void', 'xml', 'datatable', 'Dictionary`2', + 'SqlDataReader', 'datarow', 'ScriptBlock', 'type') -notcontains $type) + { + if ($Type -ieq 'OrderedDictionary') + { + $Type = 'HashTable' + } + elseif ($Type -ieq 'PSCustomObject') + { + $Type = 'PSObject' + } + elseif ($Type -ieq 'List`1') + { + $Type = 'Array' + } + elseif ($inputObject -is "Array") + { + $Type = 'Array' + } # whatever it thinks it is called + elseif ($inputObject -is "HashTable") + { + $Type = 'HashTable' + } # for our purposes it is a hashtable + elseif (!($inputObject | Get-Member -membertype Properties | Select-Object name | Where-Object name -like 'Keys')) + { + $Type = 'generic' + } #use dot notation + elseif (($inputObject | Get-Member -membertype Properties | Select-Object name).count -gt 1) + { + $Type = 'Object' + } + } + write-verbose "$($padding)Type:='$Type', Object type:=$($inputObject.GetType().Name), BaseName:=$($inputObject.GetType().BaseType.Name) " + + switch ($Type) + { + 'ScriptBlock'{ "{$($inputObject.ToString())}" } + 'InnerXML' { "|`r`n" + ($inputObject.OuterXMl.Split("`r`n") | ForEach-Object{ "$padding$_`r`n" }) } + 'DateTime' { $inputObject.ToString('s') } # s=SortableDateTimePattern (based on ISO 8601) using local time + 'Byte[]' { + $string = [System.Convert]::ToBase64String($inputObject) + if ($string.Length -gt 100) + { + # right, we have to format it to YAML spec. + '!!binary "\' + "`r`n" # signal that we are going to use the readable Base64 string format + #$bits = @() + $length = $string.Length + $IndexIntoString = 0 + $wrap = 100 + while ($length -gt $IndexIntoString + $Wrap) + { + $padding + $string.Substring($IndexIntoString, $wrap).Trim() + "`r`n" + $IndexIntoString += $wrap + } + if ($IndexIntoString -lt $length) + { + $padding + $string.Substring($IndexIntoString).Trim() + "`r`n" + } + else + { + "`r`n" + } + } + + else + { + '!!binary "' + $($string -replace '''', '''''') + '"' + } + + } + 'Boolean' { + "$(&{ + if ($inputObject -eq $true) { 'true' } + else { 'false' } + })" + } + 'string' { + $String = "$inputObject" + if ($string -match '[\r\n]' -or $string.Length -gt 80) + { + # right, we have to format it to YAML spec. + $folded = ">`r`n" # signal that we are going to use the readable 'newlines-folded' format + $string.Split("`n") | ForEach-Object { + $length = $_.Length + $IndexIntoString = 0 + $wrap = 80 + while ($length -gt $IndexIntoString + $Wrap) + { + $BreakPoint = $wrap + $earliest = $_.Substring($IndexIntoString, $wrap).LastIndexOf(' ') + $latest = $_.Substring($IndexIntoString + $wrap).IndexOf(' ') + if (($earliest -eq -1) -or ($latest -eq -1)) + { + $BreakPoint = $wrap + } + elseif ($wrap - $earliest -lt ($latest)) + { + $BreakPoint = $earliest + } + else + { + $BreakPoint = $wrap + $latest + } + + if (($wrap - $earliest) + $latest -gt 30) + { + $BreakPoint = $wrap # in case it is a string without spaces + } + + $folded += $padding + $_.Substring($IndexIntoString, $BreakPoint).Trim() + "`r`n" + $IndexIntoString += $BreakPoint + } + + if ($IndexIntoString -lt $length) + { + $folded += $padding + $_.Substring($IndexIntoString).Trim() + "`r`n`r`n" + } + else + { + $folded += "`r`n`r`n" + } + } + $folded + } + else + { + "'$($string -replace '''', '''''')'" + } + } + 'Char' { "([int]$inputObject)" } + { + @('byte', 'decimal', 'double', 'float', 'single', 'int', 'int32', 'int16', ` + 'long', 'int64', 'sbyte', 'uint16', 'uint32', 'uint64') -contains $_ + } + { "$inputObject" } # rendered as is without single quotes + 'PSNoteProperty' { "$(ConvertTo-YAML -inputObject $inputObject.Value -depth $depth -NestingLevel ($NestingLevel + 1))" } + 'Array' { "$($inputObject | Foreach-Object { "`r`n$padding- $(ConvertTo-YAML -inputObject $_ -depth $depth -NestingLevel ($NestingLevel + 1))" })" } + 'HashTable'{ + ("$($inputObject.GetEnumerator() | Foreach-Object { + "`r`n$padding $($_.Name): " + + (ConvertTo-YAML -inputObject $_.Value -depth $depth -NestingLevel ($NestingLevel + 1)) + })") + } + 'Dictionary`2'{ + ("$($inputObject.GetEnumerator() | Foreach-Object { + "`r`n$padding $($_.Key): " + + (ConvertTo-YAML -inputObject $_.Value -depth $depth -NestingLevel ($NestingLevel + 1)) + })") + } + 'PSObject' { ("$($inputObject.PSObject.Properties | Foreach-Object { "`r`n$padding $($_.Name): " + (ConvertTo-YAML -inputObject $_ -depth $depth -NestingLevel ($NestingLevel + 1)) })") } + 'generic' { "$($inputObject.Keys | Foreach-Object { "`r`n$padding $($_): $(ConvertTo-YAML -inputObject $inputObject.$_ -depth $depth -NestingLevel ($NestingLevel + 1))" })" } + 'Object' { ("$($inputObject | Get-Member -membertype properties | Select-Object-Object name | Foreach-Object { "`r`n$padding $($_.name): $(ConvertTo-YAML -inputObject $inputObject.$($_.name) -depth $NestingLevel -NestingLevel ($NestingLevel + 1))" })") } + 'XML' { ("$($inputObject | Get-Member -membertype properties | Where-Object-object { @('xml', 'schema') -notcontains $_.name } | Select-Object-Object name | Foreach-Object { "`r`n$padding $($_.name): $(ConvertTo-YAML -inputObject $inputObject.$($_.name) -depth $depth -NestingLevel ($NestingLevel + 1))" })") } + 'DataRow' { ("$($inputObject | Get-Member -membertype properties | Select-Object-Object name | Foreach-Object { "`r`n$padding $($_.name): $(ConvertTo-YAML -inputObject $inputObject.$($_.name) -depth $depth -NestingLevel ($NestingLevel + 1))" })") } + <# + 'SqlDataReader'{ $all = $inputObject.FieldCount + while ($inputObject.Read()) {for ($i = 0; $i -lt $all; $i++) + {"`r`n$padding $($Reader.GetName($i)): $(ConvertTo-YAML -inputObject $($Reader.GetValue($i)) -depth $depth -NestingLevel ($NestingLevel+1))"}} + #> + default { "'$inputObject'" } + } + } + catch + { + write-error "Error'$($_)' in script $($_.InvocationInfo.ScriptName) $($_.InvocationInfo.Line.Trim()) (line $($_.InvocationInfo.ScriptLineNumber)) char $($_.InvocationInfo.OffsetInLine) executing $($_.InvocationInfo.MyCommand) on $type object '$($inputObject)' Class: $($inputObject.GetType().Name) BaseClass: $($inputObject.GetType().BaseType.Name) " + } + finally { } + } + + END { } +} \ No newline at end of file diff --git a/PSYaml/en-US/about_PSYaml.help.txt b/PSYaml/en-US/about_PSYaml.help.txt new file mode 100644 index 0000000..2435f50 --- /dev/null +++ b/PSYaml/en-US/about_PSYaml.help.txt @@ -0,0 +1,11 @@ +PSTOPIC + about_PSYaml + +SHORT DESCRIPTION + + +LONG DESCRIPTION + + +DETAILED DESCRIPTION + diff --git a/PSYaml/lib/YamlDotNet.dll b/PSYaml/lib/YamlDotNet.dll new file mode 100644 index 0000000..d570fcb Binary files /dev/null and b/PSYaml/lib/YamlDotNet.dll differ diff --git a/README.md b/README.md index 64d3dd3..82bf9da 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,73 @@ -This is a PowerShell module that allows you to convert between PowerShell and YAML objects. -You can do this - - import-module psyaml - $yamlString =@" - invoice: !!str 34843 - date : 2001-01-23 - approved: yes - bill-to: - given : Chris - family : Dumars - address: - lines: | - 458 Walkman Dr. - Suite #292 - city : Royal Oak - state : MI - postal : 48046 - ship-to: id001 - product: - - sku : BL394D - quantity : 4 - description : Basketball - price : 450.00 - - sku : BL4438H - quantity : 1 - description : Super Hoop - price : 2392.00 - tax : 251.42 - total: 4443.52 - comments: > - Late afternoon is best. - Backup contact is Nancy - Billsmer @ 338-4338. - - "@ - $YamlObject=ConvertFrom-YAML $yamlString - ConvertTo-YAML $YamlObject - -For all the background and details see the documentation +# PSYaml + +## Build Status + +|Branch | Status | +|-------|:--------:| +|Master |[![AppVeyor build status](https://ci.appveyor.com/api/projects/status/github/pezhore/PSYaml?branch=master&svg=true)](https://ci.appveyor.com/project/pezhore/PSYaml/branch/master)| +|release/stage |[![AppVeyor build status](https://ci.appveyor.com/api/projects/status/github/pezhore/PSYaml?branch=release/stage&svg=true)](https://ci.appveyor.com/project/pezhore/PSYaml/branch/release/stage)| + + + +## Introduction +This module will help users convert from/to YAML. For more information see [the documentation](./Documentation.adoc). + +Please note that the **Master** branch has the latest, ready-for-production version. The **release/stage** branch is the holding ground for master merges where integration testing will take place. Other branches with active development will be denoted by having a prefix ( **feature/**, **bugfix/**, **release/**, etc) followed by an unique identifier. Nothing is merged into **release/stage** branch without code review, and only code that passes testing in the **release/stage** branch will be merged into **master**. + +## Features +* Fancy Logo +* Bundled binary +* Pester Testing + +## ToDo +* Clean up documentation + +## Example Usage +```PowerShell +import-module psyaml +$yaml = @" +anArray: +- 1 +- 2 +- 3 +nested: + array: + - this + - is + - an + - array +hello: world +"@ +$YamlObject = ConvertFrom-YAML $yamlString +ConvertTo-YAML $YamlObject +``` + +## Contact Information +Author: Phil-Factor, Brian Marsh + +## Release Notes +| Version | Change Log | +| :-------: | ----------------------------------------------------------------- | +| 1.0.2 | Reformated several sections for readability, added pester tests | +| 1.0.1 | Converted single psm1 file to multiple public/private functions | + +## Installation +### One time setup +* Download/clone the repository +* Copy the PSYaml folder to a module path (e.g. `$env:USERPROFILE\Documents\WindowsPowerShell\Modules\`) +* Alternatively, in the PS-PSYaml folder use PSDeploy (`Invoke-PSDeploy -Path .\PSDeploy\`) + +### Automated Install +* Assuming you have PowerShell v5 and a Nuget Repository configured, use the built in Module management (`Install-Module PSYaml`) + +## Import the module. +`Import-Module PSYaml #Alternatively, Import-Module \\Path\To\PSYaml` + +## Get commands in the module +`Get-Command -Module PSYaml` + +## Get help +`Get-Help ConvertFrom-Yaml -Full` +`Get-Help about_PSYaml` + + diff --git a/Tests/Helpers/Convert-PSObjectToHashtable.ps1 b/Tests/Helpers/Convert-PSObjectToHashtable.ps1 new file mode 100644 index 0000000..a5e8977 --- /dev/null +++ b/Tests/Helpers/Convert-PSObjectToHashtable.ps1 @@ -0,0 +1,36 @@ +function Convert-PSObjectToHashtable +{ + param ( + [Parameter(ValueFromPipeline)] + $InputObject + ) + + process + { + if ($null -eq $InputObject) { return $null } + + if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string]) + { + $collection = @( + foreach ($object in $InputObject) { Convert-PSObjectToHashtable $object } + ) + + Write-Output -NoEnumerate $collection + } + elseif ($InputObject -is [psobject]) + { + $hash = @{} + + foreach ($property in $InputObject.PSObject.Properties) + { + $hash[$property.Name] = Convert-PSObjectToHashtable $property.Value + } + + $hash + } + else + { + $InputObject + } + } +} diff --git a/Tests/PSYaml.ConvertFrom.Tests.ps1 b/Tests/PSYaml.ConvertFrom.Tests.ps1 new file mode 100644 index 0000000..961e356 --- /dev/null +++ b/Tests/PSYaml.ConvertFrom.Tests.ps1 @@ -0,0 +1,157 @@ +# This module's name +$ModuleName = "PSYaml" + +# Get this instance of PowerShell's major version +$PSVersion = $PSVersionTable.PSVersion.Major + +#Force Import this repo's module +Import-Module "$($PSScriptRoot)\..\$($ModuleName)\$($ModuleName).psm1" -Force +$libPath = "$($PSScriptRoot)\..\$($ModuleName)\lib\YamlDotNet.dll" +Add-Type -Path $libPath + +$yaml = @" +anArray: +- 1 +- 2 +- 3 +nested: + array: + - this + - is + - an + - array +hello: world +"@ + +if ($PSVersion -ne 5) +{ + Describe "Should convert YAML to a PSObject in PowerShell v$($PSVersion)" { + BeforeEach { + Remove-Variable -Name PSObj -ErrorAction SilentlyContinue + + $PSObj = ConvertFrom-Yaml $yaml + } + + Context 'Strict mode' { + + Set-StrictMode -Version $PSversion + + It 'Should have three keys' { + $Keys = @('anArray','nested','hello') + $PSObj.Keys | ForEach-Object { $Keys -contains $_ } | Should be $True, $True, $true + } + + It 'Should have a nested array with length 4' { + $PSobj.nested.array.Length | Should be 4 + } + + It 'Should have a key/value hello world' { + $PSobj.hello | Should be 'world' + } + } + } +} + +Describe "Should convert YAML to a PSObject in PowerShell v5" { + BeforeEach { + Remove-Variable -Name PSObj -ErrorAction SilentlyContinue + + $PSObj = ConvertFrom-Yaml $yaml + } + + Context 'Strict mode' { + + Set-StrictMode -Version 5 + + It 'Should have three keys' { + $Keys = @('anArray','nested','hello') + $PSObj.Keys | ForEach-Object { $Keys -contains $_ } | Should be $True, $True, $true + } + + It 'Should have a nested array with length 4' { + $PSobj.nested.array.Length | Should be 4 + } + + It 'Should have a key/value hello world' { + $PSobj.hello | Should be 'world' + } + } +} + +Describe "Should convert YAML to a PSObject in PowerShell v4" { + BeforeEach { + Remove-Variable -Name PSObj -ErrorAction SilentlyContinue + + $PSObj = ConvertFrom-Yaml $yaml + } + + Context 'Strict mode' { + + Set-StrictMode -Version 4 + + It 'Should have three keys' { + $Keys = @('anArray','nested','hello') + $PSObj.Keys | ForEach-Object { $Keys -contains $_ } | Should be $True, $True, $true + } + + It 'Should have a nested array with length 4' { + $PSobj.nested.array.Length | Should be 4 + } + + It 'Should have a key/value hello world' { + $PSobj.hello | Should be 'world' + } + } +} + +Describe "Should convert YAML to a PSObject in PowerShell v3" { + BeforeEach { + Remove-Variable -Name PSObj -ErrorAction SilentlyContinue + + $PSObj = ConvertFrom-Yaml $yaml + } + + Context 'Strict mode' { + + Set-StrictMode -Version 3 + + It 'Should have three keys' { + $Keys = @('anArray','nested','hello') + $PSObj.Keys | ForEach-Object { $Keys -contains $_ } | Should be $True, $True, $true + } + + It 'Should have a nested array with length 4' { + $PSobj.nested.array.Length | Should be 4 + } + + It 'Should have a key/value hello world' { + $PSobj.hello | Should be 'world' + } + } +} + +Describe "Should convert YAML to a PSObject in PowerShell v2" { + BeforeEach { + Remove-Variable -Name PSObj -ErrorAction SilentlyContinue + + $PSObj = ConvertFrom-Yaml $yaml + } + + Context 'Strict mode' { + + Set-StrictMode -Version 2 + + It 'Should have three keys' { + $Keys = @('anArray','nested','hello') + $PSObj.Keys | ForEach-Object { $Keys -contains $_ } | Should be $True, $True, $true + } + + It 'Should have a nested array with length 4' { + $PSobj.nested.array.Length | Should be 4 + } + + It 'Should have a key/value hello world' { + $PSobj.hello | Should be 'world' + } + } +} \ No newline at end of file diff --git a/Tests/PSYaml.ConvertTo.Tests.ps1 b/Tests/PSYaml.ConvertTo.Tests.ps1 new file mode 100644 index 0000000..cdb3dfd --- /dev/null +++ b/Tests/PSYaml.ConvertTo.Tests.ps1 @@ -0,0 +1,145 @@ +# This module's name +$ModuleName = "PSYaml" + +# Get this instance of PowerShell's major version +$PSVersion = $PSVersionTable.PSVersion.Major + +#Force Import this repo's module +Import-Module "$($PSScriptRoot)\..\$($ModuleName)\$($ModuleName).psm1" -Force +$libPath = "$($PSScriptRoot)\..\$($ModuleName)\lib\YamlDotNet.dll" +Add-Type -Path $libPath + +$YamlText = @" + anArray: + - 1 + - 2 + - 3 + nested: + array: + - 'this' + - 'is' + - 'an' + - 'array' + hello: 'world' +"@ + +$PSObj = ConvertFrom-Yaml $YamlText + +if ($PSVersion -ne 5) +{ + Describe "Should convert a PSObject to YAML in PowerShell v$($PSVersion)" { + BeforeEach { + Remove-Variable -Name Yaml, ConvertedObj -ErrorAction SilentlyContinue + + $Yaml = ConvertTo-Yaml $PSObj + + } + + Context 'Strict mode' { + + Set-StrictMode -Version $PSversion + + It 'Should convert yaml back to PSObject' { + $ConvertedObj = ConvertFrom-Yaml $Yaml + $Output = Foreach ($Key in $PSObj.Keys) + { + Compare-Object $ConvertedObj.$Key $PSObj.$Key + } + + $Output | Should be $null + } + } + } +} + +Describe "Should convert a PSObject to YAML in PowerShell v5" { + BeforeEach { + Remove-Variable -Name Yaml, ConvertedObj -ErrorAction SilentlyContinue + + $Yaml = ConvertTo-Yaml $PSObj + } + + Context 'Strict mode' { + + Set-StrictMode -Version 5 + + It 'Should convert yaml back to PSObject' { + $ConvertedObj = ConvertFrom-Yaml $Yaml + $Output = Foreach ($Key in $PSObj.Keys) + { + Compare-Object $ConvertedObj.$Key $PSObj.$Key + } + + $Output | Should be $null + } + } +} + +Describe "Should convert a PSObject to YAML in PowerShell v4" { + BeforeEach { + Remove-Variable -Name Yaml, ConvertedObj -ErrorAction SilentlyContinue + + $Yaml = ConvertTo-Yaml $PSObj + } + + Context 'Strict mode' { + + Set-StrictMode -Version 4 + + It 'Should convert yaml back to PSObject' { + $ConvertedObj = ConvertFrom-Yaml $Yaml + $Output = Foreach ($Key in $PSObj.Keys) + { + Compare-Object $ConvertedObj.$Key $PSObj.$Key + } + + $Output | Should be $null + } + } +} + +Describe "Should convert a PSObject to YAML in PowerShell v3" { + BeforeEach { + Remove-Variable -Name Yaml, ConvertedObj -ErrorAction SilentlyContinue + + $Yaml = ConvertTo-Yaml $PSObj + } + + Context 'Strict mode' { + + Set-StrictMode -Version 3 + + It 'Should convert yaml back to PSObject' { + $ConvertedObj = ConvertFrom-Yaml $Yaml + $Output = Foreach ($Key in $PSObj.Keys) + { + Compare-Object $ConvertedObj.$Key $PSObj.$Key + } + + $Output | Should be $null + } + } +} + +Describe "Should convert a PSObject to YAML in PowerShell v2" { + BeforeEach { + Remove-Variable -Name Yaml, ConvertedObj -ErrorAction SilentlyContinue + + $Yaml = ConvertTo-Yaml $PSObj + } + + Context 'Strict mode' { + + Set-StrictMode -Version 2 + + It 'Should convert yaml back to PSObject' { + $ConvertedObj = ConvertFrom-Yaml $Yaml + $Output = Foreach ($Key in $PSObj.Keys) + { + Compare-Object $ConvertedObj.$Key $PSObj.$Key + } + + $Output | Should be $null + } + } +} \ No newline at end of file diff --git a/Tests/PSYaml.ConvertYamlToJson.Tests.ps1 b/Tests/PSYaml.ConvertYamlToJson.Tests.ps1 new file mode 100644 index 0000000..2421429 --- /dev/null +++ b/Tests/PSYaml.ConvertYamlToJson.Tests.ps1 @@ -0,0 +1,132 @@ +# This module's name +$ModuleName = "PSYaml" + +# Get this instance of PowerShell's major version +$PSVersion = $PSVersionTable.PSVersion.Major + +#Force Import this repo's module +Import-Module "$($PSScriptRoot)\..\$($ModuleName)\$($ModuleName).psm1" -Force +$libPath = "$($PSScriptRoot)\..\$($ModuleName)\lib\YamlDotNet.dll" +Add-Type -Path $libPath + +$Helpers = @( Get-ChildItem -Path $PSScriptRoot\Helpers\*.ps1 -ErrorAction SilentlyContinue ) +Foreach ($Helper in $Helpers) +{ + Try + { + . $Helper.fullname + } + Catch + { + Write-Error -Message "Failed to import function $($Helper.fullname): $_" + } +} + +$YamlText = @" + anArray: + - 1 + - 2 + - 3 + nested: + array: + - 'this' + - 'is' + - 'an' + - 'array' + hello: 'world' +"@ + +$JsonText = '{"anArray": ["1", "2", "3"], "nested": {"array": ["this", "is", "an", "array"]}, "hello": "world"}' + +if ($PSVersion -ne 5) +{ + Describe "Should convert YAML to JSON in PowerShell v$($PSVersion)" { + BeforeEach { + Remove-Variable -Name obj1, obj2 -ErrorAction SilentlyContinue + } + Context 'Strict mode' { + + Set-StrictMode -Version $PSversion + + It 'Should convert yaml to json' { + $obj1 = Convert-PSObjectToHashtable -InputObject (ConvertFrom-Json -InputObject $JsonText) + $obj2 = Convert-PSObjectToHashtable -InputObject (Convert-YamlToJson -YamlString $YamlText | ConvertFrom-Json) + + $obj1 | Should match $obj2 + + } + } + } +} + +Describe "Should convert YAML to JSON in PowerShell v5" { + BeforeEach { + Remove-Variable -Name obj1, obj2 -ErrorAction SilentlyContinue + } + + Context 'Strict mode' { + + Set-StrictMode -Version 5 + + It 'Should convert yaml to json' { + $obj1 = Convert-PSObjectToHashtable -InputObject (ConvertFrom-Json -InputObject $JsonText) + $obj2 = Convert-PSObjectToHashtable -InputObject (Convert-YamlToJson -YamlString $YamlText | ConvertFrom-Json) + $obj1 | Should match $obj2 + + } + } +} + +Describe "Should convert YAML to JSON in PowerShell v4" { + BeforeEach { + Remove-Variable -Name obj1, obj2 -ErrorAction SilentlyContinue + } + + Context 'Strict mode' { + + Set-StrictMode -Version 4 + + It 'Should convert yaml to json' { + $obj1 = Convert-PSObjectToHashtable -InputObject (ConvertFrom-Json -InputObject $JsonText) + $obj2 = Convert-PSObjectToHashtable -InputObject (Convert-YamlToJson -YamlString $YamlText | ConvertFrom-Json) + $obj1 | Should match $obj2 + + } + } +} + +Describe "Should convert YAML to JSON in PowerShell v3" { + BeforeEach { + Remove-Variable -Name obj1, obj2 -ErrorAction SilentlyContinue + } + + Context 'Strict mode' { + + Set-StrictMode -Version 3 + + It 'Should convert yaml to json' { + $obj1 = Convert-PSObjectToHashtable -InputObject (ConvertFrom-Json -InputObject $JsonText) + $obj2 = Convert-PSObjectToHashtable -InputObject (Convert-YamlToJson -YamlString $YamlText | ConvertFrom-Json) + $obj1 | Should match $obj2 + + } + } +} + +Describe "Should convert YAML to JSON in PowerShell v2" { + BeforeEach { + Remove-Variable -Name obj1, obj2 -ErrorAction SilentlyContinue + } + + Context 'Strict mode' { + + Set-StrictMode -Version 2 + + It 'Should convert yaml to json' { + $obj1 = Convert-PSObjectToHashtable -InputObject (ConvertFrom-Json -InputObject $JsonText) + $obj2 = Convert-PSObjectToHashtable -InputObject (Convert-YamlToJson -YamlString $YamlText | ConvertFrom-Json) + $obj1 | Should match $obj2 + + } + } +} \ No newline at end of file diff --git a/Tests/PSYaml.Tests.ps1 b/Tests/PSYaml.Tests.ps1 new file mode 100644 index 0000000..7baca76 --- /dev/null +++ b/Tests/PSYaml.Tests.ps1 @@ -0,0 +1,34 @@ +# This module's name +$ModuleName = "PSYaml" + +# Get this instance of PowerShell's major version +$PSVersion = $PSVersionTable.PSVersion.Major + +#Force Import this repo's module +Import-Module $PSScriptRoot\..\$ModuleName\$ModuleName.psm1 -Force + +# Specify ScriptAnalyzer rules to exclude when testing +$RulesToExclude = "PSUseDeclaredVarsMoreThanAssignments" + +## Begin our tests! + +Describe "Should pass Script Analyzer PS$PSVersion Integrations tests" { + Context 'Strict mode' { + + Set-StrictMode -Version latest + + It 'Should have no output from Script Analyzer' { + + # If we have specified rules, exclude them + if ($RulesToExclude) + { + $Output = Invoke-ScriptAnalyzer -Path $PSScriptRoot\..\$ModuleName -Recurse -ExcludeRule $RulesToExclude + } + else + { + $Output = Invoke-ScriptAnalyzer -Path $PSScriptRoot\..\$ModuleName -Recurse + } + $Output | Select-Object RuleName, Severity, ScriptName, Line, Message | Should be $null + } + } +} diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..290eadc --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,26 @@ +# See http://www.appveyor.com/docs/appveyor-yml for many more options + +# Skip on updates to the readme. +# We can force this by adding [skip ci] or [ci skip] anywhere in commit message +skip_commits: + message: /updated readme.*/ + +install: + +# Install Package Provider to install PsScriptAnalyzer + - ps: Install-Module Pester -Force + +# Install PsScriptAnalyzer to ensure we are following powershell best practices + - ps: Install-Module PsScriptAnalyzer -Force + +# We aren't building any artifacts +build: false + +# The magic for testing goes here! +test_script: +# Invoke pester, but output each test as UnitXML + - ps: $res = Invoke-Pester -Path ".\Tests" -OutputFormat NUnitXml -OutputFile TestsResults.xml -PassThru +# Upload the xml to appveyor + - ps: (New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestsResults.xml)) +# If any failed, fail the build + - ps: if ($res.FailedCount -gt 0) { throw "$($res.FailedCount) tests failed."} \ No newline at end of file