Source/Public/Reporting_Main.ps1
Set-StrictMode -Version Latest #region Function: Get-ODUExportLatestPath <# .SYNOPSIS Gets latest export full folder path that matches YYYYMMDD-HHMMSS name format .DESCRIPTION Gets latest export full folder path that matches YYYYMMDD-HHMMSS name format Grabs latest export found under: Root folder \ Octo Server Name but has format name: YYYYMMDD-HHMMSS or, more specifically: ^\d{8}-\d{6}$ If you copy & rename a folder it won't get returned unless it matches that format. .EXAMPLE Get-ODUExportLatestPath c:\OctoExports\MyOctoServer.octopus.app\20181107-185919 .LINK https://github.com/DTW-DanWard/OctopusDeployUtilities #> function Get-ODUExportLatestPath { [CmdletBinding()] [OutputType([string])] param() process { if ($false -eq (Confirm-ODUConfig)) { return } # get Octopus Server instance root folder $OctoServerRootFolderPath = Join-Path -Path (Get-ODUConfigExportRootFolder) -ChildPath (Get-ODUConfigOctopusServer).Name if ($false -eq (Test-Path -Path $OctoServerRootFolderPath)) { throw "Root server path not found, bad configuration: $OctoServerRootFolderPath" } # get latest that matches format YYYYMMDD-HHMMSS $Folder = Get-ChildItem -Path $OctoServerRootFolderPath | Where-Object { $_.Name -match '^\d{8}-\d{6}$' } | Select-Object -Last 1 if ($null -eq $Folder) { throw "No export folders matching pattern ^\d{8}-\d{6}$ (i.e. YYYYMMDD-HHMMSS) found under Server instance path: $OctoServerRootFolderPath" } $Folder.FullName } } #endregion #region Function: Get-ODUExportOlderPath <# .SYNOPSIS Gets an older (not latest) export full folder path that matches YYYYMMDD-HHMMSS name format .DESCRIPTION Gets an older (not latest) export full folder path that matches YYYYMMDD-HHMMSS name format. If no value is passed for parameter Hours it returns the most recent export path before the latest export. If an Hours value is passed it finds the first export that many hours older than the most recent export and returns that path. The folder names that are parsed/returned must match YYYYMMDD-HHMMSS name format or, more specifically: ^\d{8}-\d{6}$ If you copy & rename a folder it won't get returned unless it matches that format. .PARAMETER Hours Minimum number of hours older the export should be compared to latest export .EXAMPLE Get-ODUExportOlderPath c:\OctoExports\MyOctoServer.octopus.app\20181107-185919 .EXAMPLE Get-ODUExportOlderPath 24 c:\OctoExports\MyOctoServer.octopus.app\20181105-1132512 .LINK https://github.com/DTW-DanWard/OctopusDeployUtilities #> function Get-ODUExportOlderPath { [CmdletBinding()] [OutputType([string])] param( [ValidateScript( {$_ -ge 0})] [int]$Hours = 0 ) process { if ($false -eq (Confirm-ODUConfig)) { return } # get Octopus Server instance root folder $OctoServerRootFolderPath = Join-Path -Path (Get-ODUConfigExportRootFolder) -ChildPath (Get-ODUConfigOctopusServer).Name if ($false -eq (Test-Path -Path $OctoServerRootFolderPath)) { throw "Root server path not found, bad configuration: $OctoServerRootFolderPath" } # get latest that matches format YYYYMMDD-HHMMSS [object[]]$Folders = Get-ChildItem -Path $OctoServerRootFolderPath | Where-Object { $_.Name -match '^\d{8}-\d{6}$' } | Sort-Object -Descending if ($Folders.Count -eq 0) { throw "No export folders matching pattern ^\d{8}-\d{6}$ (i.e. YYYYMMDD-HHMMSS) found under Server instance path: $OctoServerRootFolderPath" } if ($Folders.Count -eq 1) { throw "Only one export folder matching pattern ^\d{8}-\d{6}$ (i.e. YYYYMMDD-HHMMSS) found under Server instance path: $OctoServerRootFolderPath" } # if no Hours passed we can safely return the path of the second item if ($Hours -eq 0) { # not returning first (0) item, that's the most recent export $Folders[1].FullName } else { $OlderThanTime = $Folders[1].CreationTime.AddHours(-$Hours) # filter down folders [object[]]$Folders = $Folders | Where-Object { $_.CreationTime -lt $OlderThanTime } | Select-Object -First 1 if ($null -eq $Folders -or $Folders.Count -eq 0) { throw "No export folder found older than $Hours hours (or, more specifically, older than $OlderThanTime" } else { $Folders[0].FullName } } } } #endregion #region Function: Read-ODUExportFromFile <# .SYNOPSIS Given a specific export instance folder path returns PSObject with all values of export contained as properties .DESCRIPTION Given a specific export instance folder path returns PSObject with all values of export contained as properties PSObject type layout matches folder names If FolderPath not passed, uses value from Get-ODUExportLatestPath .PARAMETER Path Path for export .EXAMPLE Read-ODUExportFromFile <returns PSObject with all exported data from latest export> .EXAMPLE Read-ODUExportFromFile c:\OctoExports\MyOctoServer.octopus.app\20181107-185919 <returns PSObject with all exported data for that particular export folder> .LINK https://github.com/DTW-DanWard/OctopusDeployUtilities #> function Read-ODUExportFromFile { [CmdletBinding()] param( [ValidateScript( { if (($null -ne $_) -and (! (Test-Path -Path $_))) { throw "Path does not exist: $_" } if (($null -ne $_) -and (! (Test-Path -Path $_ -PathType Container))) { throw "Path must be a folder, not a file: $_" } return $true })] [string]$Path ) process { if ($false -eq (Confirm-ODUConfig)) { return } # if no path passed, use latest if ($null -eq $Path -or ($Path.Trim() -eq '') ) { $Path = (Get-ODUExportLatestPath) } # confirm projects type folders found # make sure standard export type folders exist under path; if less than half, probably wrong path - don't process [string[]]$StandardExportFolders = @('DeploymentProcesses', 'Environments', 'LibraryVariableSets', 'Machines', 'Projects', 'Variables') $FoundCount = ($StandardExportFolders | Where-Object { Test-Path -Path (Join-Path -Path $Path -ChildPath $_) } | Measure-Object).Count if ($FoundCount -lt ([math]::Floor(($StandardExportFolders.Count) / 2))) { throw "This does not appear to be a proper export folder - less than half of the standard folders ($StandardExportFolders) were found at $Path - is this a proper export?" } $ExportData = [ordered]@{} [int]$Throttle = Get-ODUConfigBackgroundJobsMax # just in case a sneaky user manually edited the config file to go higher than 9 if ($Throttle -gt 9) { $Throttle = 9 } $Jobs = Get-ChildItem -Path $Path -Directory | Start-RSJob -Throttle $Throttle -ScriptBlock { Param($Directory) # return results in hash table with Directory name and objects $Results = @{ Name = $Directory.Name } Write-Verbose "$($MyInvocation.MyCommand) :: Reading folder $TypeName" $Data = [System.Collections.ArrayList]@() (Get-ChildItem -Path $Directory.FullName -Recurse -Include ('*' + $JsonExtension)).foreach( { $Content = Get-Content -Path $_ -Raw if ($null -ne $Content) { $null = $Data.Add((ConvertFrom-Json -InputObject $Content)) } }) # add Data to results object and return $Results.Data = $Data $Results } $null = Wait-RSJob -Job $Jobs # there could be errors; collect all of them first and remove jobs before throwing errors or # other jobs will never get removed [object[]]$Errors = $null $Jobs | ForEach-Object { $Job = $_ if ($Job.HasErrors) { $Errors += Select-Object -InputObject $Job -ExpandProperty Error } else { $TypeData = Receive-RSJob -Job $Job $ExportData.($TypeData.Name) = $TypeData.Data } $null = Remove-RSJob -Job $Job } if (($null -ne $Errors) -and ($Errors.Count -gt 0)) { $Errors | ForEach-Object { throw $_ } } [PSCustomObject]$ExportData } } #endregion |