vroide.psm1
Class VroActionInput { [string] $name [string] $description [string] $type } Class VroAction { [guid] $Id [string] $Name [string] $Description [string] $FQN [String] $Version [VroActionInput[]] $InputParameters [string] $OutputType [string] $Href [System.Object[]] $Relations [string] $Script [string] $Module [string] $TagsGlobal [string] $TagsUser [string] $AllowedOperations [string] modulePath ($basePath) { return (Join-Path -Path $basePath -ChildPath $this.FQN.Split("/")[0]) } [string] filePath ($basePath, [string]$fileExtension) { return (Join-Path -Path $basePath -ChildPath $this.FQN.Split("/")[0] -AdditionalChildPath "$($this.Name).$fileExtension") } } function ConvertFrom-VroActionXml { param ( [Parameter( Mandatory = $true, ValueFromPipelineByPropertyName = $true )] [ValidateNotNull()] [xml]$InputObject ) $xml = $InputObject # Init $vroAction = [VroAction]::new(); # process xml $vroAction.Name = $xml.'dunes-script-module'.name $vroAction.Description = $xml.'dunes-script-module'.description.'#cdata-section' $vroAction.OutputType = $xml.'dunes-script-module'.'result-type' $vroAction.Script = $xml.'dunes-script-module'.script.'#cdata-section' $vroAction.Version = $xml.'dunes-script-module'.version if ($xml.'dunes-script-module'.'allowed-operations'){ $vroAction.AllowedOperations = $xml.'dunes-script-module'.'allowed-operations' } if ($xml.'dunes-script-module'.'id' -as [guid]){ $vroAction.Id = $xml.'dunes-script-module'.'id' }else{ $vroAction.Id = $xml.'dunes-script-module'.'id'.substring(0,4) + $xml.'dunes-script-module'.'id'.substring(32,4) + $xml.'dunes-script-module'.'id'.substring(41,24) } if ($xml.'dunes-script-module'.param) { $inputs = @() foreach ($input in $xml.'dunes-script-module'.param) { $obj = [VroActionInput]::new() $obj.name = $input.n $obj.description = $input.'#cdata-section' $obj.type = $input.t $inputs += $obj } $vroAction.InputParameters = $inputs } return $vroAction } function ConvertTo-VroActionJs { param ( [Parameter( Mandatory = $true, ValueFromPipelineByPropertyName = $true )] [ValidateNotNull()] [VroAction]$InputObject ) # being compiling JS file $vroActionJs = "/**" + [System.Environment]::NewLine # add in description if available if ($InputObject.Description){ foreach ($line in $InputObject.Description.split([System.Environment]::NewLine)){ $vroActionJs += "* " + $line + [System.Environment]::NewLine } } # add inputs if available if ($InputObject.InputParameters){ foreach ($input in $InputObject.InputParameters) { $vroActionJs += "* @param {" + $input.type + "} " + $input.name + " - " + $input.description + [System.Environment]::NewLine } } # additional fields $vroActionJs += "* @id " + $InputObject.Id + [System.Environment]::NewLine $vroActionJs += "* @version " + $InputObject.Version + [System.Environment]::NewLine $vroActionJs += "* @allowedoperations " + $InputObject.AllowedOperations + [System.Environment]::NewLine # compulsory return field $vroActionJs += "* @return {" + $InputObject.OutputType + "}" + [System.Environment]::NewLine $vroActionJs += "*/" + [System.Environment]::NewLine # add in function with inputs by name $vroActionJs += "function " + $InputObject.Name + "(" $vroActionJs += ($InputObject.InputParameters.name) -join "," $vroActionJs += ") {" + [System.Environment]::NewLine if ($InputObject.Script) { foreach ($line in $InputObject.Script.split([System.Environment]::NewLine)) { $vroActionJs += "`t$line" + [System.Environment]::NewLine } } $vroActionJs += "};" return $vroActionJs } function ConvertFrom-VroActionJs { param ( [Parameter( Mandatory = $true, ValueFromPipelineByPropertyName = $true )] [ValidateNotNull()] [string[]]$InputObject ) # above validations including from pipeline # check there is header start header end function and final line # Init $vroAction = [VroAction]::new(); # Regex Extractor $patternHeader = '(?smi)\/\*\*\n(\* .*\n)+(\*\/)' $patternDescription = "(\/\*\*\n)(\* [^@\n]*[^@]*)(\n)" $patternBody = "(?smi)^function .*\n(.*\n)*" $patternInputs = "(?smi)\* @(?<jsdoctype>param) (?<type>[^}]*}) (?<name>\w+) - (?<description>[^\n]*)" $patternReturn = "(?smi)\* @(?<jsdoctype>return) (?<type>{[^}]*})" $patternId = "(?smi)\* @(?<jsdoctype>id) (?<description>[^\n]*)" $patternAllowedOperations = "(?smi)\* @(?<jsdoctype>allowedoperations) (?<description>[^\n]*)" $patternVersion = "(?smi)\* @(?<jsdoctype>version) (?<description>[^\n]*)" $jsdocBody = ($InputObject | Select-String -Pattern $patternBody | ForEach-Object { $_.Matches.value }).split([System.Environment]::NewLine) $vroAction.Name = $jsdocBody[0].split(" ")[1].split("(")[0] $vroAction.Script = ($jsdocBody | Select-Object -Skip 1 | Select-Object -First ($jsdocBody.count - 3) | ForEach-Object { $_ -replace "^\t","" }) -join [System.Environment]::NewLine $jsdocHeader = $InputObject | Select-String $patternHeader -AllMatches | ForEach-Object { $_.Matches } | ForEach-Object { $_.Groups[1] } | ForEach-Object { $_.Value } $jsDocDescription = $InputObject | Select-String -Pattern $patternDescription -AllMatches | ForEach-Object { $_.Matches } | ForEach-Object { $_.Groups[2] } | ForEach-Object { $_.Value } $vroAction.Description = $jsDocDescription -replace "(?ms)^\* ","" # jsdoc comments $jsdocComments = @() $jsdocHeader | Select-String -AllMatches -Pattern $patternInputs | Foreach-Object { foreach ($MatchItem in $_.Matches){ $jsdocComments += [PSCustomObject] @{ jsdoctype = $MatchItem.Groups['jsdoctype'].Value type = $MatchItem.Groups['type'].Value name = $MatchItem.Groups['name'].Value description = $MatchItem.Groups['description'].Value } } } $jsdocHeader | Select-String -AllMatches -Pattern $patternReturn | Foreach-Object { foreach ($MatchItem in $_.Matches){ $jsdocComments += [PSCustomObject] @{ jsdoctype = $MatchItem.Groups['jsdoctype'].Value type = $MatchItem.Groups['type'].Value } } } $jsdocHeader | Select-String -AllMatches -Pattern $patternId | Foreach-Object { foreach ($MatchItem in $_.Matches){ $jsdocComments += [PSCustomObject] @{ jsdoctype = $MatchItem.Groups['jsdoctype'].Value description = $MatchItem.Groups['description'].Value } } } $jsdocHeader | Select-String -AllMatches -Pattern $patternAllowedOperations | Foreach-Object { foreach ($MatchItem in $_.Matches){ $jsdocComments += [PSCustomObject] @{ jsdoctype = $MatchItem.Groups['jsdoctype'].Value description = $MatchItem.Groups['description'].Value } } } $jsdocHeader | Select-String -AllMatches -Pattern $patternVersion | Foreach-Object { foreach ($MatchItem in $_.Matches){ $jsdocComments += [PSCustomObject] @{ jsdoctype = $MatchItem.Groups['jsdoctype'].Value description = $MatchItem.Groups['description'].Value } } } # Populate vroaction $id = $jsdocComments | Where-Object { $_.jsdoctype -eq "id" } if ($id){ $vroAction.Id = $id.description }else{ $vroAction.Id = "{$([guid]::NewGuid().Guid)}".ToUpper() } # inputs $inputs = @() foreach ($input in ($jsdocComments | Where-Object { $_.jsdoctype -eq "param" })) { $obj = [VroActionInput]::new() $obj.name = $input.name $obj.description = $input.description $obj.type = $input.type $inputs += $obj } $vroAction.InputParameters = $inputs # version $vroAction.Version = ($jsdocComments | Where-Object { $_.jsdoctype -eq "version" }).description # allowed operations $vroAction.AllowedOperations = ($jsdocComments | Where-Object { $_.jsdoctype -eq "allowedoperations" }).description # return type $vroAction.OutputType = ($jsdocComments | Where-Object { $_.jsdoctype -eq "return" }).type return $vroAction } function ConvertTo-VroActionXml { param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNull()] [VroAction]$inputObject ) # move as many tests as possible up to the top # consider output type - currently this script will be null, but maybe it should be the jsdoc path $vroActionXml = [xml]'<?xml version="1.0" encoding="UTF-8"?>' $xmlElt = $vroActionXml.CreateElement("dunes-script-module") $att = $vroActionXml.CreateAttribute("name") $att.Value = $inputObject.Name $null = $xmlElt.Attributes.Append($att) $att = $vroActionXml.CreateAttribute("result-type") $att.Value = $inputObject.OutputType.replace("{","").replace("}","") $null = $xmlElt.Attributes.Append($att) $att = $vroActionXml.CreateAttribute("api-version") $att.Value = "6.0.0" $null = $xmlElt.Attributes.Append($att) $att = $vroActionXml.CreateAttribute("id") $att.Value = $inputObject.Id $null = $xmlElt.Attributes.Append($att) $att = $vroActionXml.CreateAttribute("version") $att.Value = $inputObject.Version $null = $xmlElt.Attributes.Append($att) if (!([string]::IsNullOrWhitespace($inputObject.AllowedOperations))){ $att = $vroActionXml.CreateAttribute("allowed-operations") $att.Value = $inputObject.AllowedOperations $null = $xmlElt.Attributes.Append($att) } $null = $vroActionXml.AppendChild($xmlElt) $Node = $vroActionXml.'dunes-script-module' # Creation of a node and its text if ($inputObject.Description){ $xmlElt = $vroActionXml.CreateElement("description") $xmlCdata = $vroActionXml.CreateCDataSection($inputObject.Description) $null = $xmlElt.AppendChild($xmlCdata) # Add the node to the document $null = $Node.AppendChild($xmlElt) } ## Populate Inputs from Component Plan foreach ($Input in $inputObject.InputParameters){ # Creation of a node and its text $xmlElt = $vroActionXml.CreateElement("param") $xmlCdata = $vroActionXml.CreateCDataSection($Input.description) $null = $xmlElt.AppendChild($xmlCdata) # Creation of an attribute in the principal node $xmlAtt = $vroActionXml.CreateAttribute("n") $xmlAtt.value = $Input.name $null = $xmlElt.Attributes.Append($xmlAtt) # Creation of an attribute in the principal node $xmlAtt = $vroActionXml.CreateAttribute("t") $xmlAtt.value = $Input.type.trim("{").trim("}") $null = $xmlElt.Attributes.Append($xmlAtt) # Add the node to the document $null = $Node.AppendChild($xmlElt) } if ($inputObject.Script){ # Creation of a node and its text $xmlElt = $vroActionXml.CreateElement("script") $xmlCdata = $vroActionXml.CreateCDataSection($inputObject.Script -join [System.Environment]::NewLine) $null = $xmlElt.AppendChild($xmlCdata) # Creation of an attribute in the principal node $xmlAtt = $vroActionXml.CreateAttribute("encoded") $xmlAtt.value = "false" $null = $xmlElt.Attributes.Append($xmlAtt) # Add the node to the document $null = $Node.AppendChild($xmlElt) } return $vroActionXml } function Export-VroActionFile { param ( [Parameter( Mandatory = $true, ValueFromPipelineByPropertyName = $true )] [ValidateNotNull()] [string[]]$InputObject, [Parameter( ValueFromPipelineByPropertyName = $true )] [string]$exportFolder ) # create temporary folder $TempDir = [System.Guid]::NewGuid().ToString() $tmpWorkingFolder = New-Item -Path (New-TemporaryFile).DirectoryName -Type Directory -Name $TempDir $compressFolder = New-Item -Path $tmpWorkingFolder.fullName -Name "$($vroActionXml.'dunes-script-module'.name).action" -Type Directory #code $tmpWorkingFolder # export content xml $vroActionXml.Save("$compressFolder/action-content") $actionContent = get-content "$compressFolder/action-content" $actionContent = $actionContent | ForEach-Object { $_.replace("<?xml version=`"1.0`" encoding=`"UTF-8`"?>","<?xml version='1.0' encoding='UTF-8'?>") } $actionContent | set-content "$compressFolder/action-content" -Encoding bigendianunicode $stream = [IO.File]::OpenWrite("$compressFolder/action-content") $stream.SetLength($stream.Length - 2) $stream.Close() $stream.Dispose() # export history xml $actionHistory = "<?xml version='1.0' encoding='UTF-8'?>" + [System.Environment]::NewLine $actionHistory += "<items>" + [System.Environment]::NewLine $actionHistory += "</items>" + [System.Environment]::NewLine $actionHistory | Set-Content "$compressFolder/action-history" -Encoding bigendianunicode # export info xml $actionInfo = "#" + [System.Environment]::NewLine $actionInfo += "#Wed Jul 24 04:55:53 UTC 2019" + [System.Environment]::NewLine $actionInfo += "unicode=true" + [System.Environment]::NewLine $actionInfo += "owner=" + [System.Environment]::NewLine $actionInfo += "version=2.0" + [System.Environment]::NewLine $actionInfo += "type=action" + [System.Environment]::NewLine $actionInfo += "creator=www.dunes.ch" + [System.Environment]::NewLine $actionInfo += "charset=UTF-16" + [System.Environment]::NewLine $actionInfo | Set-Content "$compressFolder/action-info" -Encoding utf8 # compress the folder #$compressedFolder = Compress-Archive -Path $compressFolder -DestinationPath "$exportFolder/$($vroActionXml.'dunes-script-module'.name).action" #-Force $compressedFolder = [io.compression.zipfile]::CreateFromDirectory($compressFolder, "$exportFolder/$($vroActionXml.'dunes-script-module'.name).action") return $compressedFolder } function Compare-VroActionContents { param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNull()] [String]$OriginalVroActionFile, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNull()] [string]$UpdatedVroActionFile ) # create temporary folder $TempDir = [System.Guid]::NewGuid().ToString() $tmpWorkingFolder = New-Item -Path (New-TemporaryFile).DirectoryName -Type Directory -Name $TempDir $original = New-Item -Path $tmpWorkingFolder.fullName -Name "original" -Type Directory $updated = New-Item -Path $tmpWorkingFolder.fullName -Name "updated" -Type Directory #code $tmpWorkingFolder Expand-Archive -Path $OriginalVroActionFile -DestinationPath $original Expand-Archive -Path $UpdatedVroActionFile -DestinationPath $updated ([xml](Get-Content $original/action-content)).Save("$original/action-content") ([xml](Get-Content $updated/action-content)).Save("$updated/action-content") $originalFileHash = Get-FileHash -Path $original/action-content $updatedFileHash = Get-FileHash -Path $updated/action-content # finalise Write-Debug $tmpWorkingFolder.fullName Write-Debug $originalFileHash.Hash Write-Debug $updatedFileHash.Hash if ($originalFileHash.Hash -eq $updatedFileHash.Hash){ $tmpWorkingFolder | Remove-Item -Recurse -Force -Confirm:$false return $true } # attempt number : dropping the allowed operations VEF - sometimes unpredictable outputs $diff = Compare-Object -ReferenceObject $original/action-cont -DifferenceObject $updated/action-content $vcfLineEndOriginal = (Get-Content "$original/action-content")[1].Split(" ")[-1].split("=")[0] if ( ($diff.count -eq 2) -and ( $vcfLineEndOriginal -eq "allowed-operations" ) ){ $originalFile = Get-Content $original/action-content $updatedFile = Get-Content $updated/action-content $originalFile[1] = $updatedFile[1] $originalFile | set-content "$original/action-content" -Encoding bigendianunicode $stream = [IO.File]::OpenWrite("$original/action-content") $stream.SetLength($stream.Length - 2) $stream.Close() $stream.Dispose() ([xml](Get-Content $original/action-content)).Save("$original/action-content") $originalFileHash = Get-FileHash -Path $original/action-content $updatedFileHash = Get-FileHash -Path $updated/action-content if ($originalFileHash.Hash -eq $updatedFileHash.Hash){ $tmpWorkingFolder | Remove-Item -Recurse -Force -Confirm:$false return $true } } return $false } function Export-VroIde { param ( [Parameter( Mandatory = $false )] [string]$vroIdeFolder, [switch]$keepWorkingFolder ) Write-Debug "### Beginng Export VRO IDE" if (!$vROConnection){ throw "VRO Connection Required" } if ($vroIdeFolder){ $vroIdeFolder = Get-Item $vroIdeFolder }else{ Write-Debug "No Folder Provided Generating a Random one" $vroIdeFolder = CreateTemporaryFolder } $workingFolder = New-Item -ItemType Directory -Path $vroIdeFolder -Name "$([guid]::NewGuid().Guid)".ToUpper() $vroActionHeaders = Get-vROAction | Where-Object { $_.FQN -notlike "com.vmware*" } # export vro action headers $vroActionHeaders | ConvertTo-Json | set-content (Join-Path -Path $vroIdeFolder -ChildPath "vroActionHeaders.json") # Creating Folders foreach ($vroActionHeader in $vroActionHeaders){ $vroActionHeader = $vroActionHeader -as [VroAction] Write-Debug "Creating Folders : $($vroActionHeader.FQN)" if (!(Test-Path $vroActionHeader.modulePath($vroIdeFolder))){ $null = New-Item -ItemType Directory -Path $vroActionHeader.modulePath($vroIdeFolder) } if (!(Test-Path $vroActionHeader.modulePath($workingFolder))){ $null = New-Item -ItemType Directory -Path $vroActionHeader.modulePath($workingFolder) } } # Downloading Actions foreach ($vroActionHeader in $vroActionHeaders){ $vroActionHeader = $vroActionHeader -as [VroAction] Write-Debug "Downloading Action : $($vroActionHeader.FQN)" $null = Export-vROAction -Id $vroActionHeader.Id -Path $vroActionHeader.modulePath($workingFolder) } # Expanding Actions foreach ($vroActionHeader in $vroActionHeaders){ $vroActionHeader = $vroActionHeader -as [VroAction] Write-Debug "Expanding Action : $($vroActionHeader.FQN)" Add-Type -AssemblyName System.IO.Compression.FileSystem $actionContentFile = [System.IO.Compression.ZipFile]::OpenRead($vroActionHeader.filePath($workingFolder,"action")).Entries | Where-Object { $_.FullName -eq "action-content"} [System.IO.Compression.ZipFileExtensions]::ExtractToFile($actionContentFile, $vroActionHeader.filePath($workingFolder,"xml"), $true) } # Import XML convert to jsdoc convert save foreach ($vroActionHeader in $vroActionHeaders){ $vroActionHeader = $vroActionHeader -as [VroAction] Write-Debug "Convert from XML to JS and Save for Action : $($vroActionHeader.FQN)" $vroActionXml = [xml](get-content $vroActionHeader.filePath($workingFolder,"xml")) $vroAction = ConvertFrom-VroActionXml -InputObject $vroActionXml $vroAction | ConvertTo-Json -Depth 99 | Set-Content $vroActionHeader.filePath($workingFolder,"json") $vroActionJs = ConvertTo-VroActionJs -InputObject $vroAction $vroActionJs | set-content $vroActionHeader.filePath($vroIdeFolder,"js") } if ($keepWorkingFolder){ Write-Debug "Working Folder not deleted : $($workingFolder.FullName)" }else{ $null = Remove-Item $workingFolder -Recurse -Force -Confirm:$false } return $vroIdeFolder } function Import-VroIde { param ( [Parameter( Mandatory = $true )] [string]$vroIdeFolder, [switch]$keepWorkingFolder ) Write-Debug "### Beginng Import VRO IDE" if (!$vROConnection){ throw "VRO Connection Required" } if (!(Test-Path "$vroIdeFolder/vroActionHeaders.json")){ throw "vroActionHeaders.json file required in the working folder" }else{ $vroActionHeaders = Get-Content (Join-Path -Path $vroIdeFolder -ChildPath "vroActionHeaders.json") -Raw | ConvertFrom-Json } $vroActionHeaders | Select-Object -First 5 $workingFolder = New-Item -ItemType Directory -Path $vroIdeFolder -Name ([guid]::NewGuid().Guid).ToUpper() # Creating Folders foreach ($vroActionHeader in $vroActionHeaders){ $vroActionHeader = $vroActionHeader -as [VroAction] Write-Debug "Creating Folders : $($vroActionHeader.FQN)" if (!(Test-Path $vroActionHeader.modulePath($vroIdeFolder))){ $null = New-Item -ItemType Directory -Path $vroActionHeader.modulePath($vroIdeFolder) } if (!(Test-Path $vroActionHeader.modulePath($workingFolder))){ $null = New-Item -ItemType Directory -Path $vroActionHeader.modulePath($workingFolder) } } # Downloading Actions foreach ($vroActionHeader in $vroActionHeaders){ $vroActionHeader = $vroActionHeader -as [VroAction] Write-Debug "Downloading Action : $($vroActionHeader.FQN)" $null = Export-vROAction -Id $vroActionHeader.Id -Path $vroActionHeader.modulePath($workingFolder) } # Import jsodc convert to xml convert save and export to action foreach ($vroActionHeader in $vroActionHeaders){ $vroActionHeader = $vroActionHeader -as [VroAction] Write-Debug "Convert from XML to JS and Save for Action : $($vroActionHeader.FQN)" $vroActionJs = Get-Content $vroActionHeader.filePath($vroIdeFolder,"js") -Raw $vroAction = ConvertFrom-VroActionJs -InputObject $vroActionJs $vroAction | ConvertTo-Json -Depth 99 | Set-Content $vroActionHeader.filePath($workingFolder,"json") $vroActionXml = ConvertTo-VroActionXml -InputObject $vroAction $vroActionXml.Save($vroActionHeader.filePath($workingFolder,"xml")) Export-VroActionFile -InputObject $vroActionXml -exportFolder $vroActionHeader.modulePath($vroIdeFolder) } # Compare and upload on difference foreach ($vroActionHeader in $vroActionHeaders){ $vroActionHeader = $vroActionHeader -as [VroAction] $compareResult = Compare-VroActionContents -OriginalVroActionFile $vroActionHeader.filePath($workingFolder,"action") -UpdatedVroActionFile $vroActionHeader.filePath($vroIdeFolder,"action") -Debug if ($compareResult){ Write-Debug "Comparing $($vroActionHeader.Name) : would not be updated - file hash identical" }else{ Write-Debug "Comparing $($vroActionHeader.Name) : would be updated - file hash not identical" Import-vROAction -CategoryName $vroActionHeader.FQN.split("/")[0] -File $vroActionHeader.filePath($vroIdeFolder,"action") #-Overwrite -WhatIf } Remove-Item -Path $vroActionHeader.filePath($vroIdeFolder,"action") -Confirm:$false } if ($keepWorkingFolder){ Write-Debug "Working Folder not deleted : $($workingFolder.FullName)" }else{ $null = Remove-Item $workingFolder -Recurse -Force -Confirm:$false } } |