Files/Setup/SetupComplete.ps1


$setupFolder = $PSScriptRoot
$logFilePath = Join-Path $setupFolder log.txt

#######################################
#region Functions
#######################################

function Write-Log {
    param (
        [string]$Level,
        [string]$Message
    )

    $timestamp = [System.DateTime]::Now.TimeOfDay.ToString()

    # FATAL, ERROR, WARNING, INFO, DEBUG, TRACE
    if (!$Level) {
        $Level = "INFO"    
    }
    $formattedMessage = "$timestamp - $($Level.PadLeft(8)) - $Message"

    switch ($level) {
        "FATAL"   { Write-Host $formattedMessage -ForegroundColor White -BackgroundColor Red }
        "ERROR"   { Write-Host $formattedMessage -ForegroundColor White -BackgroundColor Red }
        "WARNING" { Write-Host $formattedMessage -ForegroundColor Yellow }
        "INFO"    { Write-Host $formattedMessage -ForegroundColor White }
        "DEBUG"   { Write-Host $formattedMessage -ForegroundColor Cyan }
        "TRACE"   { Write-Host $formattedMessage -ForegroundColor Gray }
        default   { Write-Host $formattedMessage -ForegroundColor White }
    }

    if ($logFilePath) {
        Add-content $logFilePath -value $formattedMessage
    }
}

function Convert-PSObjectToHashtable {
    param (
        [Parameter(  
             Position = 0,   
             ValueFromPipeline = $true,  
             ValueFromPipelineByPropertyName = $true  
         )]
        [object]$InputObject
    )

    if (-not $InputObject) {
        return $null
    }

    if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string]) {
        $output = @(
            foreach ($item in $InputObject) {
                Convert-PSObjectToHashtable $item
            }
        )

        Write-Output -NoEnumerate $output
    }
    elseif ($InputObject -is [psobject]) {
        $output = @{}
        $InputObject | Get-Member -MemberType *Property | % { 
            $output.($_.name) = Convert-PSObjectToHashtable $InputObject.($_.name)
        } 
        $output
    }
    else {
        $InputObject
    }
}

function Get-AvailableDriveLetter {
    param(
       [parameter(Mandatory=$False)]
       [Switch]
       $ReturnFirstLetterOnly
   )
 
   $volumeList = Get-Volume
   # Get all available drive letters, and store in a temporary variable.
   $usedDriveLetters = @(Get-Volume | % { "$([char]$_.DriveLetter)"}) + @(Get-WmiObject -Class Win32_MappedLogicalDisk| %{$([char]$_.DeviceID.Trim(':'))})
   $tempDriveLetters = @(Compare-Object -DifferenceObject $usedDriveLetters -ReferenceObject $( 67..90 | % { "$([char]$_)" } ) | ? { $_.SideIndicator -eq '<=' } | % { $_.InputObject })
 
   # For completeness, sort the output alphabetically
   $availableDriveLetter = ($tempDriveLetters | Sort-Object)
   if ($ReturnFirstLetterOnly -eq $true)
   {
      $tempDriveLetters[0]
   }
   else
   {
      $tempDriveLetters
   }
}

#endregion
#######################################

try {
    Write-Log "INFO" "Starting setup-complete script in '$setupFolder'"
    Write-Log "INFO" "Running as '$($env:USERNAME)'"

    #######################################
    #region Initialize PowerShell environment
    #######################################
    Write-Log "INFO" "Setting execution policy"
    Set-ExecutionPolicy Unrestricted -Force
    Write-Log "INFO" "Finished setting execution policy"
    #endregion
    #######################################

    #######################################
    #region Enable PS-Remoting
    #######################################
    Write-Log "INFO" "Enabling PowerShell remoting"
    Enable-PSRemoting -SkipNetworkProfileCheck -Force -Confirm:$false
    Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WSMAN\Service\' -Name 'allow_unencrypted' -Value 0x1
    Set-Item WSMan:\localhost\Client\TrustedHosts * -Force
    Set-Item WSMan:\localhost\Client\AllowUnencrypted $true -Force
    Restart-Service winrm
    Write-Log "INFO" "Finished enabling PowerShell remoting"
    #endregion
    #######################################

    #######################################
    #region Enable CredSSP
    #######################################
    Write-Log "INFO" "Enabling CredSSP authentication"
    Enable-WSManCredSSP -Role Server -Force | Out-Null
    Enable-WSManCredSSP -Role Client -DelegateComputer * -Force | Out-Null
    Write-Log "INFO" "Finished enabling CredSSP authentication"
    #endregion
    #######################################

    #######################################
    #region Install modules
    #######################################
    $modulesPath = Join-Path -Path $setupFolder -ChildPath 'Modules'
    if (Test-Path -Path $modulesPath -PathType Container) {
        $destination = Join-Path -Path $env:ProgramFiles -ChildPath 'WindowsPowerShell\Modules'
        Write-Log "INFO" "installing modules from '$modulesPath' to '$destination'"
        Get-ChildItem -Path "$modulesPath\*" -include '*.nupkg','*.zip' |% {
            if ($_.BaseName -match '(?<name>\D*)\.(?<version>\d*.\d*(.\d*(.\d*)?)?)') {
                Write-Log "INFO" "Installing module '$($_.BaseName)'"
                if ([System.IO.Path]::GetExtension($_.FullName) -ne '.zip') {
                    $zipFilePath = Join-Path -Path $_.Directory -ChildPath "$($_.BaseName).zip"
                    Rename-Item -Path $_.FullName -NewName $zipFilePath
                }
                else {
                    $zipFilePath = $_.FullName
                }

                $destinationPath = Join-Path -Path $destination -ChildPath "$($Matches.name)\$($Matches.version)"
                New-Item -Path $destinationPath -ItemType Directory -Force | Out-Null
                Expand-Archive -Path $zipFilePath -DestinationPath $destinationPath -Force

                get-childitem -path $destinationPath |? { $_.BaseName -in 'package', '_rels','[Content_Types]' } | Remove-Item -Recurse -Force

                if ($zipFilePath -ne $_.FullName) {
                    Rename-Item -Path $zipFilePath -NewName $_.FullName
                }
            }
        }
    }
    else {
        Write-Log "INFO" "Modules path '$modulesPath' not found"
    }
    #endregion
    #######################################

    #######################################
    #region Load configuration
    #######################################
    Write-Log "INFO" "Loading configuration"
    $configurationFilePath = Join-Path -Path $setupFolder -ChildPath configuration.json
    if (Test-Path -Path $configurationFilePath -PathType Leaf) {
        $configuration = Get-Content -Path $configurationFilePath -Raw | ConvertFrom-Json
        Write-Log "INFO" "Finished loading configuration"
    }
    else {
        Write-Log "INFO" "Finished loading configuration"
    }
    #endregion
    #######################################

    #######################################
    #region Extra disk
    #######################################
    Write-Log "INFO" "Checking offline disk(s)"
    $offlineDisks = Get-Disk | Where { $_.OperationalStatus -eq "Offline" }
    if ($offlineDisks) {
        Write-Log "INFO" "Processing $($offlineDisks.Length) disks"
        foreach ($offlineDisk in $offlineDisks) {
            Write-Log "INFO" "Bringing disk '$($offlineDisk.Model)' ($($offlineDisk.Size)) online"
            Set-Disk -Number $offlineDisk.Number -IsOffline $false
            Write-Log "INFO" "Disk is online"

            if (!(Get-Partition -DiskNumber $offlineDisk.Number -ErrorAction SilentlyContinue)) {
                Write-Log "INFO" "Creating partition"
                # TODO: use driveletter from configuration
                $driveLetter = Get-AvailableDriveLetter -ReturnFirstLetterOnly
                $offlineDisk | Initialize-Disk -PartitionStyle GPT
                $offlineDisk | New-Partition -UseMaximumSize -DriveLetter $driveLetter
                $offlineDisk | Get-Partition | Format-Volume -FileSystem NTFS -NewFileSystemLabel Data -Confirm:$false
                Write-Log "INFO" "Partition created ($($driveLetter):)"
            }
         }
    }
    else {
        Write-Log "INFO" "No offline disk(s)"
    }
    #endregion
    #######################################

    #######################################
    #region Network adapters
    #######################################
    Write-Log "INFO" "Renaming network adapters"
    foreach ($netAdapter in Get-NetAdapter) {
        Write-Log "INFO" "Renaming network adapter '$($netAdapter.Name)'"
        $networkAdapterName = (Get-NetAdapterAdvancedProperty -Name $netAdapter.Name -DisplayName 'Hyper-V Network Adapter Name').DisplayValue
        if (-not $networkAdapterName) {
            $networkAdapter = $configuration.NetworkAdapters |? { $_.StaticMacAddress -eq $netAdapter.MacAddress}
            if ($networkAdapter) {
                $networkAdapterName = $networkAdapter.Network.Name
            }
        }
        if ($networkAdapterName -and $netAdapter.Name -ne $networkAdapterName) {
            Write-Log "INFO" "Renaming network adapter '$($netAdapter.Name)' to '$networkAdapterName'"
            Rename-NetAdapter -Name $netAdapter.Name -NewName $networkAdapterName
            Write-Log "INFO" "Finished renaming network adapter '$($netAdapter.Name)' to '$networkAdapterName'"
        }
        else {
            Write-Log "INFO" "Skipping renaming network adapter '$($netAdapter.Name)'"
        }
    }
    Write-Log "INFO" "Finished renaming network adapters"
    #endregion
    #######################################

    #######################################
    #region Setup-script
    #######################################
    $setupScriptPath = Join-Path -Path $setupFolder -ChildPath 'SetupScript.ps1'
    if (Test-Path -Path $setupScriptPath -PathType Leaf) {
        Write-Log "INFO" "Executing setup-script at '$setupScriptPath'"
        . $setupScriptPath
        Write-Log "INFO" "Finished executing setup-script at '$setupScriptPath'"
    }
    else {
        Write-Log "INFO" "Setup-script at '$setupScriptPath' not found; execution skipped"
    }
    #endregion
    #######################################

    Write-Log "INFO" "Finished setup-complete script"
}
catch {
    Write-Log "ERROR" $_
}