PSForge.psm1

if((get-module | Where-Object { $_.Name -eq "Plaster" }).Count -eq 0)
{
    Import-Module Plaster
}

function addToPath
{
param(
    [string]$path
)

    if(-not ($env:PATH -split ";") -contains $path)
    {
        $env:PATH = $path,$env:PATH -join ";"
    }

}

function getEnvironmentOSVersion
{
    return [Environment]::OSVersion
}

function getOSPlatform
{

    $osPlatform = (getEnvironmentOSVersion).Platform

    if($osPlatform -like "Win*")
    {
        return "windows"
    }

    if($osPlatform -eq "Unix")
    {
        $uname = Invoke-Expression "uname"
        if($uname -eq "Darwin")
        {
            return "mac"
        }

        return ($uname).toLower()
    }

    return "unknown"

}

function isWindows
{
    return ((getOSPlatform) -eq "windows")
}

function isUnix
{
    return (@("linux","mac","freebsd","sunos","openbsd")).contains((getOSPlatform))
}

function isOnPath
{
    param(
        [Parameter(Mandatory=$True,Position=1)]
        [string]$cmd
    )

    $bin = Get-Command -ErrorAction "SilentlyContinue" $cmd
    return ($bin -ne $null)
}


function checkDependencies
{
    if(isUnix)
    {
        if(-not (isOnPath "mono"))
        {
            throw New-Object System.Exception ("PSForge has a dependency on 'mono' on Linux and MacOS - please install mono via the system package manager.")
        }
    }

    if(-not (isOnPath "ruby"))
    {
        throw New-Object System.Exception ("PSForge has a dependency on 'ruby' 2.3 or higher - please install ruby via the system package manager.")
    }

    if(-not (isOnPath "git"))
    {
        throw New-Object System.Exception ("PSForge has a dependency on 'git' - please install git via the system package manager.")
    }

    [string]$longRubyVersion = (Invoke-Expression "ruby --version").split(' ')[1]
    [double]$shortRubyVersion = ($longRubyVersion.split('.')[0,1]) -join '.'

    if($shortRubyVersion -lt 2.3)
    {
        throw New-Object System.Exception ("PSForge has a dependency on 'ruby' 2.3 or higher. Current version of ruby is ${longRubyVersion} - please update ruby via the system package manager.")
    }

}

function installRuby
{    
    $Activity = "Installing Ruby"
    $rubyURL = "https://dl.bintray.com/oneclick/rubyinstaller/ruby-2.3.3-i386-mingw32.7z"
    $rubyInstaller = "$PSScriptRoot\ruby.7z"
    Write-Progress -Activity $Activity -Status "Downloading Ruby archive" -percentComplete 20
    Invoke-WebRequest -Uri $rubyURL -OutFile $rubyInstaller 
    Write-Progress -Activity $Activity -Status "Extracting Ruby archive" -percentComplete 60
    & $PSScriptRoot\7zip\7za.exe x $rubyInstaller -o"${PSScriptRoot}" | Out-Null
    Write-Progress -Activity $Activity -percentComplete 100 -Completed
    Remove-Item $rubyInstaller
}

function isProjectRoot
{
param(
    [Parameter(Mandatory=$True,Position=1)]
    [string]$path
)
    return Test-Path "${path}\.git"
}

function getProjectRoot
{

    $projectRoot = Invoke-Expression "git rev-parse --show-toplevel"

    if(-Not (Test-Path $projectRoot))
    {
        throw New-Object System.Exception ("No .git directory found in ${PWD} or any of its parent directories.")
    }

    return $projectRoot

}

function updateBundle{

    if(-not (isOnPath "bundler"))
    {
        Invoke-Expression "gem install bundler" | Out-Null
    }

    $bundle = Start-Process -FilePath "bundle" -ArgumentList "check" -Wait -NoNewWindow -RedirectStandardOutput stdout -PassThru
    Remove-Item stdout
    if($bundle.Exitcode -ne 0)
    {
        Invoke-Expression "bundle install --path .bundle"
    }
}

function Invoke-Paket
{

    Push-Location "$(getProjectRoot)"
    
    BootstrapDSCModule
    generatePaketFiles

    if(isWindows)
    {
        $paketBin = ".paket\paket.exe"
    }
    else
    {
        $paketBin = "mono .paket\paket.exe"
    }

    $commandArgs = $args -join " "

    Invoke-Expression "$paketBin $commandArgs".TrimEnd()

    clearPaketFiles

    Pop-Location

}

function New-DSCModule
{
param(
    [Parameter(Mandatory=$True,Position=1)]
    [string]$ModuleName,
    [string[]]$ResourceNames,
    [string]$Version="1.0.0",
    [string]$Description=""
)

    CheckUserConfig

    $config = Get-DSCModuleGlobalConfig

    $Activity = "Bootstrapping Powershell DSC Module"

    $PlasterParams = @{
     TemplatePath = "$PSScriptRoot\plaster-powershell-dsc-module";
     DestinationPath = $ModuleName
     project_name = $ModuleName
     version = $Version
     full_name = $config.username
     company = $config.company
     project_short_description = $Description
    }

    Write-Progress -Activity $Activity -Status "Scaffolding module filestructure" -percentComplete 30
    Invoke-Plaster @PlasterParams -NoLogo *> $null

    Push-Location $ModuleName
    $currentDirectory = (Get-Item -Path ".\" -Verbose).FullName

    foreach ($resource in $ResourceNames)
    {
        New-DSCResource -ResourceName $resource
    }

    BootstrapDSCModule
    Write-Output "Module bootstrapped at $currentDirectory"
    Pop-Location
}

function New-DSCResource
{
param(
    [Parameter(Mandatory=$True,Position=1)]
    [string]$ResourceName
)

    Push-Location "$(getProjectRoot)"

    CheckUserConfig

    Write-Output "Scaffolding new DSC resource: $resource"

    $ModuleName = (Get-Item -Path ".\" -Verbose).BaseName

    if(-not (Test-Path "${ModuleName}.psd1"))
    {
        throw New-Object System.Exception ("'${ModuleName}.psd1' not found. Are you in the module root?")
    }

    BootstrapDSCModule

    $PlasterParams = @{
        TemplatePath = "$PSScriptRoot\plaster-powershell-dsc-resource";
        DestinationPath = "DSCResources\${ResourceName}"
        project_name = $ResourceName
    }

    Import-LocalizedData -BaseDirectory "." -FileName "${ModuleName}.psd1" -BindingVariable metadata

    $PlasterParams.company = $metadata.CompanyName
    $PlasterParams.project_short_description = $ModuleName
    $PlasterParams.full_name = $metadata.Author
    $PlasterParams.version = "1.0.0"

    Invoke-Plaster @PlasterParams -NoLogo *> $null
    Write-Output "New resource has been created at $(Get-Item DSCResources\$ResourceName)"

    Pop-Location
}

function Export-DSCModule
{
param
(
    [parameter(Mandatory = $true,Position=1)]
    [string]$Version
)
    Push-Location "$(getProjectRoot)"

    BootstrapDSCModule
    Invoke-Paket update
    Invoke-Paket pack output .\output version $version

    Pop-Location
}

function Test-DSCModule
{
param (
    [ValidateSet('create', 'converge', 'verify', 'test','destroy','login')]
    [string] $Action = 'verify',
    [switch] $Debug
)

    Push-Location "$(getProjectRoot)"

    Write-Output "Action: $Action"

    BootstrapDSCModule

    $azureRMCredentials = "$HOME/.azure/credentials"

    if( -not (Test-Path $azureRMCredentials))
    {
        throw New-Object System.Exception ("Create an azure credentials file at $HOME/.azure/.credentials as described here: https://github.com/test-kitchen/kitchen-azurerm")
    }

    if (-not (Test-Path env:AZURERM_SUBSCRIPTION)) {
        Write-Output "The environment variable AZURERM_SUBSCRIPTION has not been set."
        Write-Output ""
        Write-Output "Setting the value of AZURERM_SUBSCRIPTION"

        $firstLine,$remainingLines = Get-Content $azureRMCredentials

        $defaultValue = $firstLine -replace '[[\]]',''
        $prompt = Read-Host "Input your Azure Subscription ID [$($defaultValue)]"
        $prompt = ($defaultValue,$prompt)[[bool]$prompt]

        $env:AZURERM_SUBSCRIPTION = $prompt
    }

    $KitchenParams = $Action

    if($Debug)
    {
        $KitchenParams += " --log-level Debug"
    }

    updateBundle

    Invoke-Paket update
    Invoke-Expression "bundle exec kitchen ${KitchenParams}"

    Pop-Location

}

function Get-DSCModuleGlobalConfig
{
    $configFile = "$HOME/DSCWorkflowConfig.json"

    if(-Not (Test-Path $configFile))
    {
        return @{}
    }

    return Get-Content -Raw -Path $configFile | ConvertFrom-Json

}

function Set-DSCModuleGlobalConfig
{
    param (
        [parameter(Mandatory = $true,Position=1)]
        [string] $Key,
        [parameter(Mandatory = $true,Position=2)]
        [string] $Value
    )

    $configFile = "$HOME/DSCWorkflowConfig.json"
    $json = Get-DSCModuleGlobalConfig
    $Key = $Key.ToLower()
    $json | Add-Member NoteProperty $Key $Value -Force
    $json | ConvertTo-Json -depth 100 | Out-File $configFile -encoding utf8

}

function CheckUserConfig
{
    $config = Get-DSCModuleGlobalConfig

    if(!$config.username)
    {
        $defaultValue = $ENV:USERNAME
        $username = Read-Host "What is your username? [$($defaultValue)]"
        $username = ($defaultValue,$username)[[bool]$username]
        Set-DSCModuleGlobalConfig "username" "$username"
    }

    if(!$config.company)
    {
        $defaultValue = "None"
        $company = Read-Host "What is your company name? [$($defaultValue)]"
        $company = ($defaultValue,$company)[[bool]$company]
        Set-DSCModuleGlobalConfig "company" "$company"
    }
}

function BootstrapDSCModule
{

    $Activity = "Bootstrapping Powershell DSC Module"

    if(isWindows)
    {
        $RubyPath = "$PSScriptRoot\ruby-2.3.3-i386-mingw32\bin\"
        addToPath $RubyPath
        if(-not (Test-Path "$RubyPath\ruby.exe"))
        {
            installRuby
            . $PSScriptRoot\helper_scripts\fixRubyCertStore.ps1
        }
    }

    checkDependencies

    if(!(Test-Path ".\.git"))
    {
        Write-Progress -Activity $Activity -Status "Initialising local Git repository" -percentComplete 60
        Invoke-Expression "git init" | Out-Null
    }

    Write-Progress -Activity $Activity -percentComplete 100 -Completed

}

function clearPaketFiles
{
    Remove-Item -Recurse -Force .paket -ErrorAction SilentlyContinue
    Remove-Item -Force paket.dependencies -ErrorAction SilentlyContinue
    Remove-Item -Force paket.template -ErrorAction SilentlyContinue
}

function generatePaketFiles
{
    $ModuleName = (Get-Item -Path ".\" -Verbose).BaseName
    Import-LocalizedData -BaseDirectory "." -FileName "${ModuleName}.psd1" -BindingVariable moduleManifest
    Import-LocalizedData -BaseDirectory "." -FileName "dependencies.psd1" -BindingVariable dependenciesManifest

    Push-Location "$PSScriptRoot\paket"
    if(-not (Test-Path ".\paket.exe"))
    {
        if(isWindows)
        {
            Invoke-Expression ".\paket.bootstrapper.exe"
        }
        else
        {
            Invoke-Expression "mono .\paket.bootstrapper.exe"
        }
    }
    Pop-Location

    clearPaketFiles

    Copy-Item -Recurse $PSScriptRoot\paket .\.paket | Out-Null

    New-Item paket.dependencies | Out-Null
    New-Item paket.template | Out-Null

    ForEach($nugetFeed in $dependenciesManifest.NugetFeeds)
    {
        "source $nugetFeed" | Out-File paket.dependencies -Append -Encoding utf8
    }

    ForEach($nugetPackage in $dependenciesManifest.NugetPackages)
    {
        "nuget $nugetPackage" | Out-File paket.dependencies -Append -Encoding utf8
    }

@"
type file
id ${ModuleName}
version $($moduleManifest.ModuleVersion)
authors $($moduleManifest.Author)
description
    $($moduleManifest.Description)
files
   ${ModuleName}.psd1 ==> .
   DSCResources ==> DSCResources
dependencies
"@
 | Out-File  paket.template -Append -Encoding utf8

    ForEach($nugetPackage in $dependenciesManifest.NugetPackages)
    {

        " $(($nugetPackage -Split " ")[0]) == LOCKEDVERSION" | Out-File paket.template -Append -Encoding utf8
    }

}

Export-ModuleMember -function *-*