├── src ├── en-US │ └── images │ │ └── NewReportScreenshot.png ├── HtmlReport.psm1 ├── Public │ ├── New-Table.ps1 │ ├── New-Chart.ps1 │ └── New-Report.ps1 ├── Private │ └── _classes.ps1 ├── HtmlReport.psd1 └── Templates │ └── template.html ├── .gitignore ├── README.md ├── LICENSE └── Build.ps1 /src/en-US/images/NewReportScreenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jaykul/HtmlReport/HEAD/src/en-US/images/NewReportScreenshot.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore packages (this is for nuget stuff) 2 | packages/ 3 | # ignore output (because we "build" stuff there) 4 | output/ 5 | # Ignore version number folders (these are our release directories for testing) 6 | [0-9]*.[0-9]*.*/ -------------------------------------------------------------------------------- /src/HtmlReport.psm1: -------------------------------------------------------------------------------- 1 | $PSModuleRoot = $PSScriptRoot 2 | 3 | . $PSScriptRoot\Private\_classes.ps1 4 | . $PSScriptRoot\Public\New-Chart.ps1 5 | . $PSScriptRoot\Public\New-Table.ps1 6 | . $PSScriptRoot\Public\New-Report.ps1 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HtmlReport 2 | Making HTML reports with charts and tables, from templates, in PowerShell 3 | 4 | Still in progress, but here's an example of a report: 5 | 6 | ```posh 7 | $topVM = ps | Sort PrivateMemorySize -Descending | 8 | Select -First 10 | 9 | ForEach { ,@(($_.ProcessName + " " + $_.Id), $_.PrivateMemorySize) } 10 | 11 | $topCPU = ps | Sort CPU -Descending | 12 | Select -First 10 | 13 | ForEach { ,@(($_.ProcessName + " " + $_.Id), $_.CPU) } 14 | 15 | New-Report -Title "Piggy Processes" -Input { 16 | New-Chart Bar "Top VM Users" -input $topVm 17 | New-Chart Column "Top CPU Overall" -input $topCPU 18 | ps | Select ProcessName, Id, CPU, WorkingSet, *MemorySize | New-Table "All Processes" 19 | } > Report.html 20 | ``` 21 | 22 | ![Report Output](src/en-US/images/NewReportScreenshot.png) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Joel Bennett 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Public/New-Table.ps1: -------------------------------------------------------------------------------- 1 | function New-Table { 2 | #.Synopsis 3 | # Creates a new TableData object for rendering in New-Report 4 | #.Example 5 | # Get-ChildItem C:\Users -Directory | 6 | # Select LastWriteTime, @{Name="Length"; Expression={ 7 | # (Get-ChildItem $_.FullName -Recurse -File -Force | Measure Length -Sum).Sum 8 | # } }, Name | 9 | # New-Table -Title $Pwd -Description "Full file listing from $($Pwd.Name)" 10 | # 11 | # Collect the list of user directories and measure the size of each 12 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] 13 | [CmdletBinding()] 14 | param( 15 | # A title that goes on the top of the table 16 | [Parameter(Mandatory)] 17 | [string]$Title, 18 | 19 | # Description to go above the table 20 | [Parameter()] 21 | [string]$Description, 22 | 23 | # Data for the table (can be piped in) 24 | [Parameter(Mandatory,ValueFromPipeline)] 25 | [PSObject]$InputObject, 26 | 27 | # Emphasis value: default (unadorned), primary (highlighted), success (green), info (blue), warning (yellow), danger (red) 28 | [Parameter()] 29 | [Emphasis]$Emphasis = "primary" 30 | ) 31 | begin { 32 | $TableData = @() 33 | } 34 | process { 35 | $TableData += $InputObject 36 | } 37 | end { 38 | [Table]::new($Title, [PSObject]$TableData, $Description, $Emphasis) 39 | } 40 | } -------------------------------------------------------------------------------- /src/Public/New-Chart.ps1: -------------------------------------------------------------------------------- 1 | function New-Chart { 2 | #.Synopsis 3 | # Creates a new ChartData for New-Report 4 | #.Description 5 | # Collects ChartData for New-Report. 6 | # ChartData should be in specific shapes in order to work properly, but it depends somewhat on the chart type you're trying to create (LineChart, PieChart, ColumnChart, BarChart, AreaChart, ScatterChart, GeoChart, Timeline). There should be examples for each in the help below... 7 | #.Notes 8 | # TODO: Write examples... 9 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] 10 | [CmdletBinding()] 11 | param( 12 | # Chart Type 13 | [ChartType]$ChartType, 14 | 15 | # A title that goes on the top of the table 16 | [Parameter(Mandatory)] 17 | [string]$Title, 18 | 19 | # Description to go above the table 20 | [Parameter()] 21 | [string]$Description, 22 | 23 | # Data for the table (can be piped in) 24 | [Parameter(Mandatory,ValueFromPipeline)] 25 | [PSObject]$InputObject, 26 | 27 | # Emphasis value: default (unadorned), primary (highlighted), success (green), info (blue), warning (yellow), danger (red) 28 | [Parameter()] 29 | [Emphasis]$Emphasis = "default" 30 | ) 31 | begin { 32 | $ChartData = @() 33 | } 34 | process { 35 | $ChartData += $InputObject 36 | } 37 | end { 38 | $Chart = [Chart]::new($Title, [PSObject]$ChartData, $Description, $Emphasis) 39 | $Chart.ChartType = $ChartType 40 | $Chart 41 | } 42 | } -------------------------------------------------------------------------------- /src/Private/_classes.ps1: -------------------------------------------------------------------------------- 1 | $PSModuleRoot = $PSScriptRoot 2 | 3 | enum Emphasis { 4 | default 5 | primary 6 | success 7 | info 8 | warning 9 | danger 10 | } 11 | 12 | enum ChartType { 13 | LineChart 14 | PieChart 15 | ColumnChart 16 | BarChart 17 | AreaChart 18 | ScatterChart 19 | GeoChart 20 | Timeline 21 | } 22 | 23 | class DataWrapper { 24 | DataWrapper([string]$Title, [PSObject]$Data) { 25 | $this.Title = $Title 26 | $this.Data = $Data 27 | } 28 | 29 | DataWrapper([string]$Title, [PSObject]$Data, [string]$Description) { 30 | $this.Title = $Title 31 | $this.Description = $Description 32 | $this.Data = $Data 33 | } 34 | 35 | DataWrapper([string]$Title, [PSObject]$Data, [string]$Description, [Emphasis]$Emphasis) { 36 | $this.Title = $Title 37 | $this.Description = $Description 38 | $this.Emphasis = $Emphasis 39 | $this.Data = $Data 40 | } 41 | 42 | [string]$Title = "" 43 | [string]$Description = "" 44 | [Emphasis]$Emphasis = "default" 45 | [PSObject]$Data = $null 46 | } 47 | 48 | class Table : DataWrapper { 49 | Table([string]$Title, [PSObject]$Data) : base($Title, $Data) {} 50 | Table([string]$Title, [PSObject]$Data, [string]$Description) : base($Title, $Data, $Description) {} 51 | Table([string]$Title, [PSObject]$Data, [string]$Description, [Emphasis]$Emphasis) : base($Title, $Data, $Description, $Emphasis) {} 52 | } 53 | 54 | class Chart : DataWrapper { 55 | Chart([string]$Title, [PSObject]$Data) : base($Title, $Data) {} 56 | Chart([string]$Title, [PSObject]$Data, [string]$Description) : base($Title, $Data, $Description) {} 57 | Chart([string]$Title, [PSObject]$Data, [string]$Description, [Emphasis]$Emphasis) : base($Title, $Data, $Description, $Emphasis) {} 58 | [ChartType]$ChartType = "Line" 59 | } -------------------------------------------------------------------------------- /src/HtmlReport.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | # Script module or binary module file associated with this manifest. 3 | RootModule = 'HtmlReport.psm1' 4 | 5 | # Version number of this module. 6 | ModuleVersion = '0.1' 7 | 8 | # ID used to uniquely identify this module 9 | GUID = 'a1e21355-5da0-4cde-b055-cedc5bd85e44' 10 | 11 | # Author of this module 12 | Author = 'Joel "Jaykul" Bennett' 13 | 14 | # Company or vendor of this module 15 | CompanyName = 'HuddledMasses.org' 16 | 17 | # Copyright statement for this module 18 | Copyright = '(c) 2016 Joel "Jaykul" Bennett. All rights reserved.' 19 | 20 | # Description of the functionality provided by this module 21 | Description = 'Generate pretty HTML reports with tables and charts' 22 | 23 | # Minimum version of the Windows PowerShell engine required by this module 24 | PowerShellVersion = '5.0' 25 | 26 | # 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. 27 | FunctionsToExport = '*' 28 | 29 | # 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. 30 | # AliasesToExport = '*' 31 | 32 | # DSC resources to export from this module 33 | # DscResourcesToExport = @() 34 | 35 | # List of all modules packaged with this module 36 | # ModuleList = @() 37 | 38 | # List of all files packaged with this module 39 | # FileList = @() 40 | 41 | # 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. 42 | PrivateData = @{ 43 | 44 | PSData = @{ 45 | 46 | # Tags applied to this module. These help with module discovery in online galleries. 47 | # Tags = @() 48 | 49 | # A URL to the license for this module. 50 | # LicenseUri = '' 51 | 52 | # A URL to the main website for this project. 53 | # ProjectUri = '' 54 | 55 | # A URL to an icon representing this module. 56 | # IconUri = '' 57 | 58 | # ReleaseNotes of this module 59 | # ReleaseNotes = '' 60 | 61 | } # End of PSData hashtable 62 | 63 | } # End of PrivateData hashtable 64 | 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/Templates/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ${Title} 17 | 18 | 21 | 22 | 23 | 24 | 28 | 46 | 47 | 48 | 70 | 71 |
72 |
73 |
74 |

${Title}

75 |
76 | ${Charts} 77 |
78 |
79 |
80 | ${Tables} 81 |
82 | 83 | 84 | 85 | 87 | 88 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /src/Public/New-Report.ps1: -------------------------------------------------------------------------------- 1 | $TemplatePath = Join-Path $PSModuleRoot Templates 2 | 3 | $ChartTemplate = @' 4 |
5 |

${Title}

6 |
7 |
${Description}
8 |
9 | 14 | '@ 15 | 16 | $TableTemplate = @' 17 |
18 |
19 |
20 |

${Title}

21 |
${Description}
22 | 23 | ${data} 24 |
25 |
26 |
27 |
28 | '@ 29 | function New-Report { 30 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] 31 | [CmdletBinding()] 32 | param( 33 | # The template to use for the report (must exist in templates folder) 34 | [Parameter()] 35 | [string] 36 | $Template = "template.html", 37 | 38 | # The title of the report 39 | [Parameter(ValueFromPipelineByPropertyName)] 40 | [string] 41 | $Title, 42 | 43 | # A sentence or two describing the report 44 | [Parameter(ValueFromPipelineByPropertyName)] 45 | [string] 46 | $Description, 47 | 48 | # The author of the report 49 | [Parameter(ValueFromPipelineByPropertyName)] 50 | [string] 51 | $Author=${Env:UserName}, 52 | 53 | [Parameter(ValueFromPipeline)] 54 | $InputObject 55 | ) 56 | 57 | begin { 58 | Write-Debug "Beginning $($PSBoundParameters | Out-String)" 59 | if($Template -notmatch "\.html$") { $Template += ".html" } 60 | if(!(Test-Path $Template)) { 61 | $Template = Join-Path $TemplatePath $Template 62 | if(!(Test-Path $Template)) { 63 | Write-Error "Template file not found in Templates: $Template" 64 | } 65 | } 66 | 67 | $TemplateContent = Get-Content $Template -Raw 68 | $FinalTable = @() 69 | $FinalChart = @() 70 | $Index = 0 71 | $Finished = $false 72 | 73 | if($InputObject -is [ScriptBlock]) { 74 | $null = $PSBoundParameters.Remove("InputObject") 75 | & $InputObject | New-Report @PSBoundParameters 76 | $Finished = $true 77 | return 78 | } 79 | } 80 | 81 | process { 82 | if($Finished) { return } 83 | Write-Debug "Processing $($_ | Out-String)" 84 | if($Title) { 85 | $FinalTitle = [System.Security.SecurityElement]::Escape($Title) 86 | } 87 | if($Description) { 88 | $FinalDescription = [System.Security.SecurityElement]::Escape($Description) 89 | } 90 | if($Author) { 91 | $FinalAuthor = [System.Security.SecurityElement]::Escape($Author) 92 | } 93 | 94 | if($InputObject -is [ScriptBlock]) { 95 | $Data = & $InputObject 96 | } 97 | elseif($InputObject -is [DataWrapper]) { 98 | if($InputObject.Data -is [ScriptBlock]) { 99 | $Data = & $InputObject.Data 100 | } else { 101 | $Data = $InputObject.Data 102 | } 103 | } 104 | else { 105 | $Data = $InputObject 106 | } 107 | 108 | if($InputObject -is [Table]) { 109 | $Data = $Data | Microsoft.PowerShell.Utility\ConvertTo-Html -As Table -Fragment 110 | # Make sure each row is on a line, and the headers are called out properly 111 | Write-Verbose "Table with $($Data.Count) rows of data." 112 | $Data = "`n{0}`n`n`n{1}`n" -f $Data[2], ($Data[3..($Data.Count - 2)] -join "`n") 113 | 114 | $Table = $TableTemplate -replace '\${Title}', $InputObject.Title ` 115 | -replace '\${Description}', $InputObject.Description ` 116 | -replace '\${Emphasis}', $("panel-" + $InputObject.Emphasis) ` 117 | -replace '\${Data}', $Data 118 | 119 | $FinalTable += $Table 120 | } 121 | if($InputObject -is [Chart]) { 122 | Write-Verbose "$ChartType Chart with $($Data.Count) data.points" 123 | if($Data -isnot [string]) { 124 | # Microsoft's ConvertTo-Json doesn't handle PSObject unwrapping properly 125 | # https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/15123162-convertto-json-doesn-t-serialize-simple-objects-pr 126 | # To bypass this bug, we must round-trip through the CliXml serializer 127 | $TP = [IO.Path]::GetTempFileName() 128 | Export-CliXml -InputObject $Data -LiteralPath $TP 129 | $Data =Import-CliXml -LiteralPath $TP | ConvertTo-json 130 | Remove-Item $TP 131 | # $Data = Microsoft.PowerShell.Utility\ConvertTo-Json -InputObject $Data 132 | } 133 | 134 | $Chart = $ChartTemplate -replace '\${Title}', $InputObject.Title ` 135 | -replace '\${Description}', $InputObject.Description ` 136 | -replace '\${Emphasis}', $("panel-" + $InputObject.Emphasis) ` 137 | -replace '\${ChartType}', $InputObject.ChartType ` 138 | -replace '\${Data}', $Data ` 139 | -replace '\${id}', ($Index++) 140 | 141 | $FinalChart += $Chart 142 | } 143 | } 144 | 145 | end { 146 | if($Finished) { return } 147 | Write-Debug "Ending $($PSBoundParameters | Out-String)" 148 | $Output = $TemplateContent -replace '\${Title}', $FinalTitle ` 149 | -replace '\${Description}', $FinalDescription ` 150 | -replace '\${Author}', $FinalAuthor ` 151 | -replace '\${Tables}', ($FinalTable -join "`n`n") ` 152 | -replace '\${Charts}', ($FinalChart -join "`n") 153 | Write-Output $Output 154 | } 155 | } -------------------------------------------------------------------------------- /Build.ps1: -------------------------------------------------------------------------------- 1 | #requires -Module Configuration 2 | [CmdletBinding()] 3 | param( 4 | [Alias("PSPath")] 5 | [string]$Path = $PSScriptRoot, 6 | [string]$ModuleName = $(Split-Path $Path -Leaf), 7 | # The target framework for .net (for packages), with fallback versions 8 | # The default supports PS3: "net40","net35","net20","net45" 9 | # To only support PS4, use: "net45","net40","net35","net20" 10 | # To support PS2, you use: "net35","net20" 11 | [string[]]$TargetFramework = @("net40","net35","net20","net45"), 12 | [switch]$Monitor, 13 | [Nullable[int]]$RevisionNumber = ${Env:APPVEYOR_BUILD_NUMBER} 14 | ) 15 | 16 | $ErrorActionPreference = "Stop" 17 | Set-StrictMode -Version Latest 18 | Write-Host "BUILDING: $ModuleName from $Path" 19 | 20 | # The output path is just a temporary build location 21 | $OutputPath = Join-Path $Path output 22 | $null = mkdir $OutputPath -Force 23 | 24 | # We expect the source for the module in a subdirectory called one of three things: 25 | $SourcePath = "src", "source", ${ModuleName} | ForEach { Join-Path $Path $_ -Resolve -ErrorAction SilentlyContinue } | Select -First 1 26 | if(!$SourcePath) { 27 | Write-Warning "This Build script expects a 'Source' or '$ModuleName' folder to be alongside it." 28 | throw "Can't find module source folder." 29 | } 30 | $ManifestPath = Join-Path $SourcePath "${ModuleName}.psd1" -Resolve -ErrorAction SilentlyContinue 31 | if(!$ManifestPath) { 32 | Write-Warning "This Build script expects a '${ModuleName}.psd1' in the '$SourcePath' folder." 33 | throw "Can't find module source files" 34 | } 35 | 36 | # Figure out the new build version 37 | [Version]$Version = Get-Metadata $ManifestPath -PropertyName ModuleVersion 38 | 39 | # If the RevisionNumber is specified as ZERO, this is a release build ... 40 | # If the RevisionNumber is not specified, this is a dev box build 41 | # If the RevisionNumber is specified, we assume this is a CI build 42 | if($RevisionNumber -ge 0) { 43 | # For CI builds we don't increment the build number 44 | $Build = if($Version.Build -le 0) { 0 } else { $Version.Build } 45 | } else { 46 | # For dev builds, assume we're working on the NEXT release 47 | $Build = if($Version.Build -le 0) { 1 } else { $Version.Build + 1} 48 | } 49 | 50 | if([string]::IsNullOrEmpty($RevisionNumber)) { 51 | $Version = New-Object Version $Version.Major, $Version.Minor, $Build 52 | } else { 53 | $Version = New-Object Version $Version.Major, $Version.Minor, $Build, $RevisionNumber 54 | } 55 | 56 | # The release path is where the final module goes 57 | $ReleasePath = Join-Path $Path $Version 58 | 59 | Write-Verbose "OUTPUT Release Path: $ReleasePath" 60 | if(Test-Path $ReleasePath) { 61 | Write-Verbose " Clean up old build" 62 | Write-Verbose "DELETE $ReleasePath\" 63 | Remove-Item $ReleasePath -Recurse -Force -ErrorAction SilentlyContinue 64 | Write-Verbose "DELETE $OutputPath\build.log" 65 | Remove-Item $OutputPath\build.log -Recurse -Force -ErrorAction SilentlyContinue 66 | } 67 | 68 | ## Find dependency Package Files 69 | $PackagesConfig = (Join-Path $Path packages.config) 70 | if(Test-Path $PackagesConfig) { 71 | Write-Verbose " Copying Packages" 72 | foreach($Package in ([xml](Get-Content $PackagesConfig)).packages.package) { 73 | $LibPath = "$ReleasePath\lib" 74 | $folder = Join-Path $Path "packages\$($Package.id)*" 75 | 76 | # The git NativeBinaries are special -- we need to copy all the "windows" binaries: 77 | if($Package.id -eq "LibGit2Sharp.NativeBinaries") { 78 | $targets = Join-Path $folder 'libgit2\windows' 79 | $LibPath = Join-Path $LibPath "NativeBinaries" 80 | } else { 81 | # Check for each TargetFramework, in order of preference, fall back to using the lib folder 82 | $targets = ($TargetFramework -replace '^','lib\') + 'lib' | ForEach-Object { Join-Path $folder $_ } 83 | } 84 | 85 | $PackageSource = Get-Item $targets -ErrorAction SilentlyContinue | Select -First 1 -Expand FullName 86 | if(!$PackageSource) { 87 | throw "Could not find a lib folder for $($Package.id) from package. You may need to run Setup.ps1" 88 | } 89 | 90 | Write-Verbose "robocopy $PackageSource $LibPath /E /NP /LOG+:'$OutputPath\build.log' /R:2 /W:15" 91 | $null = robocopy $PackageSource $LibPath /E /NP /LOG+:"$OutputPath\build.log" /R:2 /W:15 92 | if($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne 1 -and $LASTEXITCODE -ne 3) { 93 | throw "Failed to copy Package $($Package.id) (${LASTEXITCODE}), see build.log for details" 94 | } 95 | } 96 | } 97 | 98 | 99 | ## Copy PowerShell source Files 100 | $ReleaseManifest = Join-Path $ReleasePath "${ModuleName}.psd1" 101 | 102 | 103 | # if the Source folder has "Public" and optionally "Private" in it, then the psm1 must be assembled: 104 | if(Test-Path (Join-Path $SourcePath Public) -Type Container){ 105 | Write-Verbose " Collating Module Source" 106 | $RootModule = Get-Metadata -Path $ManifestPath -PropertyName RootModule -ErrorAction SilentlyContinue 107 | if(!$RootModule) { 108 | $RootModule = Get-Metadata -Path $ManifestPath -PropertyName ModuleToProcess -ErrorAction SilentlyContinue 109 | if(!$RootModule) { 110 | $RootModule = "${ModuleName}.psm1" 111 | } 112 | } 113 | $null = mkdir $ReleasePath -Force 114 | $ReleaseModule = Join-Path $ReleasePath ${RootModule} 115 | Write-Verbose " Setting content for $ReleaseModule" 116 | 117 | $FunctionsToExport = Join-Path $SourcePath Public\*.ps1 -Resolve | % { [System.IO.Path]::GetFileNameWithoutExtension($_) } 118 | Set-Content $ReleaseModule (( 119 | (Get-Content (Join-Path $SourcePath Private\*.ps1) -Raw) + 120 | (Get-Content (Join-Path $SourcePath Public\*.ps1) -Raw)) -join "`r`n`r`n`r`n") -Encoding UTF8 121 | 122 | # If there are any folders that aren't Public, Private, Tests, or Specs ... 123 | if($OtherFolders = Get-ChildItem $SourcePath -Directory -Exclude Public, Private, Tests, Specs) { 124 | # Then we need to copy everything in them 125 | Copy-Item $OtherFolders -Recurse -Destination $ReleasePath 126 | } 127 | 128 | # Finally, we need to copy any files in the Source directory 129 | Get-ChildItem $SourcePath -File | 130 | Where Name -ne $RootModule | 131 | Copy-Item -Destination $ReleasePath 132 | 133 | Set-ItemProperty $ReleaseManifest -name IsReadOnly -value $false 134 | Update-Manifest $ReleaseManifest -Property FunctionsToExport -Value $FunctionsToExport 135 | } else { 136 | # Legacy modules just have "stuff" in the source folder and we need to copy all of it 137 | Write-Verbose " Copying Module Source" 138 | Write-Verbose "COPY $SourcePath\" 139 | $null = robocopy $SourcePath\ $ReleasePath /E /NP /LOG+:"$OutputPath\build.log" /R:2 /W:15 140 | if($LASTEXITCODE -ne 3) { 141 | throw "Failed to copy Module (${LASTEXITCODE}), see build.log for details" 142 | } 143 | } 144 | 145 | ## Touch the PSD1 Version: 146 | Write-Verbose " Update Module Version" 147 | 148 | ## Bump the version (note that this doesn't change source code, you're still responsible) 149 | Update-Metadata -Path $ReleaseManifest -PropertyName 'ModuleVersion' -Value $Version 150 | 151 | ## Validate 152 | Write-Host "`nVALIDATE: ScriptAnalyzer on $ReleasePath" 153 | Invoke-ScriptAnalyzer $ReleasePath -Recurse 154 | 155 | Write-Output (Get-Item $ReleasePath) --------------------------------------------------------------------------------