Public/Initialize-Module.ps1

. "$PSScriptRoot/../Private/Shared-Res.ps1"

function Initialize-Module {
param (
  [Parameter(Mandatory = $false)]
  [string]
  $Path = (Read-Host -prompt "Root path where project should be scaffolded (./): "),
  [Parameter(Mandatory = $true, HelpMessage = "May only be made up of numbers, letters, and some special characters. Regex that passes: ^[\w\d._-]+$")]
  [string]
  [ValidatePattern("^[\w\d._-]+$")]
  $ModuleName,
  [Parameter(Mandatory = $false)]
  [string]
  $Author = (Read-Host -prompt "Author of Project (N/A): "),
  [Parameter(Mandatory = $false)]
  [string]
  $Description = (Read-Host -prompt "Project Description (N/A): "),
  [Parameter(Mandatory = $false)]
  [string]
  $PowershellVersion = (Read-Host -prompt "Powershell Version (5.1): "),
  [Parameter(Mandatory = $false)]
  [string]
  $ModuleVersion = (Read-Host -prompt "Starting version of this project (0.1): "),
  [Parameter(Mandatory = $false)]
  [string]
  $CompanyName = (Read-Host -prompt "Company name (N/A): "),
  [Parameter(Mandatory = $false)]
  [string]
  $CopyRight = (Read-Host -prompt "Copy right (N/A): ")
)
function Invoke-Scaffold {
  [CmdletBinding()]
  param ()
  process {
    try {
      # $pv = [System.Version]::Parse($PowershellVersion)
      # $mv = [System.Version]::Parse($ModuleVersion)
      if (($null -eq $Path) -or '' -eq $Path) {
        $Path = "./"
      }
      if (($null -eq $Author) -or '' -eq $Author) {
        $Author = "N/A"
      }
      if (($null -eq $Description) -or '' -eq $Description) {
        $Description = "N/A"
      }
      if (($null -eq $PowershellVersion) -or '' -eq $PowershellVersion) {
        $PowershellVersion = "5.1"
      }
      if (($null -eq $ModuleVersion) -or '' -eq $ModuleVersion) {
        $ModuleVersion = "0.1"
      }
      if (($null -eq $CompanyName) -or '' -eq $CompanyName) {
        $CompanyName = "N/A"
      }
      if (($null -eq $CopyRight) -or '' -eq $CopyRight) {
        $CopyRight = "N/A"
      }

      # Create the module and private function directories
      mkdir $Path\$ModuleName
      mkdir $Path\$ModuleName\Private
      mkdir $Path\$ModuleName\Public
      mkdir $Path\$ModuleName\en-US # For about_Help files
      mkdir $Path\$ModuleName\Tests
      mkdir $Path\$ModuleName\settings
      mkdir $Path\$ModuleName\settings\test
      mkdir $Path\$ModuleName\settings\prod

      $appConfigEndPath = "appsettings.json"
      $lastStateEndPath = "lastState.json"
      $appConfig = "$Path\$ModuleName\settings\test\$appConfigEndPath"
      $lastStateConfig = "$Path\$ModuleName\settings\test\$lastStateEndPath"
      $appConfigProd = "$Path\$ModuleName\settings\prod\$appConfigEndPath"
      $lastStateConfigProd = "$Path\$ModuleName\settings\prod\$lastStateEndPath"
      $privateConfigEndPath = "Private\secrets.json"
      $privateConfig = "$Path\$ModuleName\$privateConfigEndPath"
      $blackListedFileName = "BlackListedVariables.txt"

      #Create the module and related files
      New-Item "$Path\$ModuleName\$ModuleName.psm1" -ItemType File
      New-Item "$Path\$ModuleName\$ModuleName.Format.ps1xml" -ItemType File
      New-Item "$Path\$ModuleName\en-US\about_$ModuleName.help.txt" -ItemType File
      New-Item "$Path\$ModuleName\Tests\$ModuleName.Tests.ps1" -ItemType File
      New-Item "$Path\$ModuleName\Private\ErrorHandler.ps1" -ItemType File
      New-Item "$Path\$ModuleName\Private\LogHelper.ps1" -ItemType File
      New-Item "$Path\$ModuleName\Private\Program.ps1" -ItemType File
      New-Item "$Path\$ModuleName\Public\Invoke-$ModuleName.ps1" -ItemType File
      New-Item "$Path\$ModuleName\$blackListedFileName" -ItemType File
      New-Item $appConfig -ItemType File
      New-Item $lastStateConfig -ItemType File
      New-Item $appConfigProd -ItemType File
      New-Item $lastStateConfigProd -ItemType File
      New-Item $privateConfig -ItemType File
      New-Item "$Path\$ModuleName\.gitignore" -ItemType File
      New-ModuleManifest -Path $Path\$ModuleName\$ModuleName.psd1 `
        -RootModule "$ModuleName.psm1" `
        -Description $Description `
        -PowerShellVersion $PowershellVersion `
        -Author $Author `
        -CompanyName $CompanyName `
        -ModuleVersion $ModuleVersion `
        -FunctionsToExport "*" `
        -CmdletsToExport "*" `
        -Copyright $CopyRight
        # -FormatsToProcess "$ModuleName.Format.ps1xml" `

      $moduleString = @'
#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): $_"
    }
}
 
# 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-ModuleMember -Function $Public.Basename
'@


      $moduleString | Out-File -FilePath "$Path\$ModuleName\$ModuleName.psm1" -Encoding utf8

      $unitTestString = @'
$PSVersion = $PSVersionTable.PSVersion.Major
# import the file with the functions you are testing
# . $PSScriptRoot"/../../Public/Get-Num.ps1"
Describe "<name_of_function1> PS$PSVersion Integrations tests" {
  Context "Strict mode" {
    Set-StrictMode -Version latest
    It "should get valid data" {
      # Simple Mock example
      # Mock Get-PrivateNum {
      # return 8
      # }
      # Simple assertion
      # $actual = Get-Num -n -10
      # $actual | Should Not Be $null Because "it is always defined."
      # $expected = -2
      # $actual | Should Be $expected Because "the value is 2"
    }
  }
}
# Describe "<name_of_function2> PS$PSVersion Integrations tests" {
# Context "Strict mode" {
# Set-StrictMode -Version latest
# It "should get valid data" {
# }
# }
# }
'@


      $unitTestString | Out-File -FilePath "$Path\$ModuleName\Tests\$ModuleName.Tests.ps1" -Encoding utf8

      $logingNotes = Get-LoggingNotes

      $mainFile = @"
$logingNotes
# See the black listed variables file to see what variables to not reassign:
# By default, if you try and reassign a black listed variable, it will throw an error.
# It is possible to override the value with force, but it is highly recommended not to!
# $ModuleName/$blackListedFileName
 
function Program {
  #[CmdletBinding()]
  #param (
    # [Parameter(Mandatory = `$false)]
    # [ValidateRange([int]::MinValue, 0)]
    # [int]
    # `$n = 0
  #)
 
  # Imports files from same directory as this file
  . `$PSScriptRoot"/LogHelper.ps1"
  . `$PSScriptRoot"/ErrorHandler.ps1"
 
  return 0
}
"@


      $mainFile | Out-File -FilePath "$Path\$ModuleName\Private\Program.ps1" -Encoding utf8

      $startTimeInfo = Get-StartTimeInfo

      $logCleanupStep = Get-LogCleanupStep

      $runMainFile = @"
# Only edit this file if you intend to write a powershell module or need to use secrets or change the environment
# If you intend to use this as a powershell project, then edit the program file in the private directory
 
function Invoke-$ModuleName {
  [CmdletBinding()]
  param ()
 
  # Makes powershell stricter by default to make code safer and more reliable
  Set-StrictMode -Version 3
 
  # Import statements (follows the bash style dot sourcing notation)
  . `$PSScriptRoot"/../Private/Program.ps1"
  . `$PSScriptRoot"/../Private/ErrorHandler.ps1"
  . `$PSScriptRoot"/../Private/LogHelper.ps1"
 
  # The environment to use. Determines the app config and state objects to use
  New-Variable -Name environ -Value `$("test") -Option ReadOnly,AllScope -Force
  New-Variable -Name settingsFolder -Value `$("`$PSScriptRoot\..\settings") -Option ReadOnly,AllScope -Force
  New-Variable -Name appConfig -Value `$(Get-Content -Path "`$settingsFolder\`$environ\appsettings.json" -Raw | ConvertFrom-Json) -Option ReadOnly,AllScope -Force
  # Secrets object. Things that you do not want to put in git go inside this. Add to the secrets json in the private folder
  # Need to uncomment this line if you want to use secrets. You will likely need to create the file aswell.
  # New-Variable -Name secrets -Value `$(`$PSScriptRoot"\..\$privateConfigEndPath") -Option ReadOnly,AllScope -Force
 
  New-Variable -Name lastStateFilePath -Value `$("`$settingsFolder\`$environ\$lastStateEndPath") -Option ReadOnly,AllScope -Force
  New-Variable -Name lastState -Value `$(Get-Content -Path `$lastStateFilePath -Raw | ConvertFrom-Json) -Option ReadOnly,AllScope -Force
 
  New-Variable -Name thisScriptName -Value `$(`$MyInvocation.MyCommand.Name -replace ".ps1", "") -Option ReadOnly,AllScope -Force
  New-Variable -Name logFolder -Value `$("`$PSScriptRoot/../logs/`$thisScriptName") -Option ReadOnly,AllScope -Force
  $startTimeInfo
  # Create log directory if it does not exist, does not destroy the folder if it exists already
  New-Item -ItemType Directory -Force -Path "`$logFolder/`$logDate/`$(`$appConfig.runFolderName)" | Out-Null
  New-Item -ItemType Directory -Force -Path "`$logFolder/`$logDate/`$(`$appConfig.summaryFolderName)" | Out-Null
 
  New-Variable -Name logFile -Value `$("`$logFolder/`$logDate/`$(`$appConfig.runFolderName)/`$(`$appConfig.logFileName)_`$(`$logTime)_log.txt") -Option ReadOnly,AllScope -Force
  New-Variable -Name summaryFile -Value `$("`$logFolder/`$logDate/`$(`$appConfig.summaryFolderName)/`$(`$appConfig.logFileName)_log.txt") -Option ReadOnly,AllScope -Force
  New-Variable -Name keepLogsForNDays -Value `$(`$appConfig.keepLogsForNDays) -Option ReadOnly,AllScope -Force
 
  `$msg = "Starting process. `$(Get-Date)``n"
  `$msg += "environment: `$environ"
  Write-Txt -txt `$msg
  Write-Json -label "appConfig:" -data `$appConfig
 
  try {
    # Program is where you should write your normal powershell script code
    Program -ErrorAction Stop
  }
 
  catch {
    `$errorDetails = Get-ErrorDetails -error `$_
    Write-Json -label "Top level issue: " -data `$errorDetails
    throw `$_
  }
 
  finally {
    `$msg = "Finished process. `$(Get-Date)``n"
    Write-Txt -txt `$msg
    $logCleanupStep
    # update last state json
    `$lastState | ConvertTo-Json | Out-File -FilePath "`$lastStateFilePath" -Encoding utf8
  }
}
 
# Remove the following line if you are trying to write a powershell module that is importable in other powershell projects/modules
Invoke-$ModuleName -ErrorAction Stop
 
"@


      $runMainFile | Out-File -FilePath "$Path\$ModuleName\Public\Invoke-$ModuleName.ps1" -Encoding utf8

      $blackListedFileContent = Get-BlackListedVars
      $blackListedFileContent | Out-File -FilePath "$Path\$ModuleName\$blackListedFileName" -Encoding utf8

      $logHelper = Get-LogCleaner
      $logWriter = Get-LogWriter
      $logHelper | Out-File -FilePath "$Path\$ModuleName\Private\LogHelper.ps1" -Encoding utf8
      $logWriter | Out-File -FilePath "$Path\$ModuleName\Private\LogHelper.ps1" -Encoding utf8 -Append

      $errorHandler = Get-ErrorHelperContent
      $errorHandler | Out-File -FilePath "$Path\$ModuleName\Private\ErrorHandler.ps1" -Encoding utf8

      $appJson = @"
{
  "preview": true,
  "keepLogsForNDays": 14,
  "logFileName": "$($ModuleName)",
  "summaryFolderName": "summary",
  "runFolderName": "per_run"
}
"@

      $lastStateJson = @"
{
  "state": "Any state from the last run of this program (or last update of this file) that is required for this run."
}
"@

      $privateConfigJson = @"
{
  "password": "not_put_in_git"
}
"@


      $appJson | Out-File -FilePath "$appConfig" -Encoding utf8
      $lastStateJson | Out-File -FilePath "$lastStateConfig" -Encoding utf8
      $privateConfigJson | Out-File -FilePath "$privateConfig" -Encoding utf8

      $appJson | Out-File -FilePath "$appConfigProd" -Encoding utf8
      $lastStateJson | Out-File -FilePath "$lastStateConfigProd" -Encoding utf8

      $privateConfigEndPath -replace "\\", "/" | Out-File -FilePath "$Path\$ModuleName\.gitignore" -Encoding utf8
      $gitignore_content = "@
logs/*
.vscode
bin/
obj/
.ionide/
/debug/
 
# Visual Studio IDE directory
.vs/
 
# Ignore executables
*.exe
*.msi
*.appx
*.msix
 
# Ignore binaries and symbols
*.pdb
*.dll
*.wixpdb
@"

      $gitignore_content | Out-File -FilePath "$Path\$ModuleName\.gitignore" -Encoding utf8 -Append
      # Copy the public/exported functions into the public folder, private functions into private folder

    }
    catch {
      Write-Error $_
    }
  }
}
Invoke-Scaffold -ErrorAction Stop
}