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-ErrorDetail -error `$_
    Write-Json -label "Top level issue: " -data `$errorDetails
    throw `$_
  }

  finally {
    `$lastState.lastRun = (Get-Date).ToString(`$appConfig.dateFormat)
    `$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",
  "dateFormat": "yyyy/MM/dd HH:mm:ss"
}
"@

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

        $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
}