DSCResources/POSHOrigin_vSphere_VM/Provisioners/Chef/Provision.ps1

[cmdletbinding()]
param(
    [parameter(mandatory)]
    $Options
)

begin {
    Write-Debug -Message 'Chef provisioner: beginning'
}

process {
    try {
        Write-Verbose -Message 'Configuring Chef client...'
        $provOptions = ConvertFrom-Json -InputObject $Options.Provisioners
        $chefOptions = $provOptions | Where-Object {$_.name -eq 'chef'}

        $t = Get-VM -Id $Options.vm.Id -Verbose:$false -Debug:$false
        $ip = $t.Guest.IPAddress | Where-Object { ($_ -notlike '169.*') -and ( $_ -notlike '*:*') } | Select-Object -First 1
        if ($null -ne $ip -and $ip -ne [string]::Empty) {
            $cmd = {
                $VerbosePreference = 'Continue'
                try {
                    $options = $args[0]
                    $provOptions = $args[1].options
                    $source = $provOptions.source
                    $sourceName = 'chef-client.msi'
                    $validatorKey = $provOptions.validatorKey
                    $validatorName = $validatorKey.split('/') | Select-Object -Last 1
                    $cert = $provOptions.cert
                    $certName = $cert.split('/') | Select-Object -Last 1
                    $runList = $provOptions.runList

                    # Ensure Chef node name is always lowercase
                    $fqdn = $provOptions.nodeName
                    $fqdnlower = $fqdn.ToLower()

                    $chefSvc = Get-Service -Name chef-client -ErrorAction SilentlyContinue
                    $chefGotInstalled = $false
                    if ($null -eq $chefSvc) {
                        Write-Verbose -Message 'Installing Chef client...'
                        # Copy Chef items locally
                        New-Item -Path "C:\Windows\Temp\ChefClient" -ItemType Directory -Force
                        Invoke-WebRequest -Uri $source -OutFile "c:\windows\temp\ChefClient\$sourceName"
                        Invoke-WebRequest -Uri $validatorKey -OutFile "c:\windows\temp\ChefClient\validator.pem"
                        Invoke-WebRequest -Uri $cert -OutFile "c:\windows\temp\ChefClient\$certName"

                        # Install Chef
                        $params = @{
                            FilePath = 'msiexec'
                            ArgumentList = '/qn /i c:\windows\temp\ChefClient\' + $sourceName + ' ADDLOCAL="ChefClientFeature,ChefServiceFeature"'
                            Wait = $true
                        }
                        Start-Process @params

                        # Add Chef to env vars
                        If ($env:Path -notmatch 'C:\\opscode\\chef\\bin' -and $env:Path -notmatch 'c:\\opscode\\chef\\embedded\\bin') {
                            [Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\opscode\chef\bin;C:\opscode\chef\embedded\bin", [System.EnvironmentVariableTarget]::Machine)
                            $env:Path = $env:Path + ";C:\opscode\chef\bin;C:\opscode\chef\embedded\bin"
                        }

                        # This doesn't work
                        #$fqdn = $provOptions.nodeName.ToLower()

                        $url = $provOptions.url
                        $validatorClientName = $validatorName.split('.')[0]
                        $knifeRB= @"
current_dir = File.dirname(__FILE__)
log_level :info
log_location STDOUT
node_name "$fqdnlower"
client_key "c:\\chef\\client.pem"
validation_client_name "$validatorClientName"
validation_key "c:\\chef\\validator.pem"
chef_server_url "$url"
cookbook_path ["C:\\chef_cookbooks"]
"@


                        $clientRB = @"
chef_server_url "$url"
validation_client_name "$validatorClientName"
validation_key "c:\\chef\\validator.pem"
client_key "c:\\chef\\client.pem"
node_name '$fqdnlower'
"@

                        New-Item -Path "$HOME\.chef" -ItemType Directory -ErrorAction SilentlyContinue -Force
                        $knifeRB | Out-File -FilePath "$HOME\.chef\knife.rb" -Encoding ascii -Force
                        $clientRB | Out-File -FilePath 'c:\chef\client.rb' -Encoding ascii -Force

                        # Copy certs
                        New-Item -Path "$HOME\.chef\trusted_certs" -ItemType Directory -ErrorAction SilentlyContinue
                        New-Item -Path 'c:\chef\trusted_certs' -Type Directory -Force -ErrorAction SilentlyContinue
                        Copy-Item -Path "c:\windows\temp\ChefClient\$certName" -Destination 'c:\chef\trusted_certs' -Force
                        Copy-Item -Path "c:\windows\temp\ChefClient\$certName" -Destination "$HOME\.chef\trusted_certs" -Force
                        Copy-Item -Path "c:\windows\temp\ChefClient\validator.pem" -Destination 'c:\chef' -Force

                        # Start Chef as service
                        Start-Process -FilePath 'chef-service-manager' -ArgumentList '-a install' -NoNewWindow -Wait
                        Start-Process -FilePath 'chef-service-manager' -ArgumentList '-a start' -NoNewWindow -Wait
                        Start-Process -FilePath 'chef-client' -NoNewWindow -Wait
                        
                        # Cleanup
                        #Remove-Item -Path "c:\chef\validator.pem" -Force
                        #Remove-Item -Path "c:\chef\$clientName"
                        Remove-Item -Path 'c:\windows\temp\chefclient\' -Recurse -Force

                        $chefGotInstalled = $true
                    }

                    # Add Chef to env vars
                    If ($env:Path -notmatch 'C:\\opscode\\chef\\bin' -and $env:Path -notmatch 'c:\\opscode\\chef\\embedded\\bin') {
                        [Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\opscode\chef\bin;C:\opscode\chef\embedded\bin", [System.EnvironmentVariableTarget]::Machine)
                        $env:Path = $env:Path + ";C:\opscode\chef\bin;C:\opscode\chef\embedded\bin"
                    }

                    # Sleep a little if we just installed Chef
                    if ($chefGotInstalled) {
                        Write-Verbose -Message 'Chef installed. Sleeping...'
                        Start-Sleep -Seconds 5
                    }

                    # Lets do additional tests to verify the run list, environment, and attributes
                    Write-Verbose -Message 'Getting Chef node...'
                    $json = knife node show $provOptions.nodename -m -F json
                    $node = $json | ConvertFrom-Json

                    # Verify run list matches
                    if ($provOptions.runlist) {
                        if (@($node.run_list).Count -ne @($provOptions.runlist).Count) {
                            # Current run list
                            $currList = @($node.run_list) | Sort
                            if ($null -eq $currList) { $currList = @()}

                            # Desired run list
                            $configList = @($provOptions.runlist) | ForEach-Object {
                                if ($_.recipe) {
                                    "recipe[$($_.recipe)]"
                                } elseif ($_.role) {
                                    "role[$($_.role)]"
                                }
                            }
                            if ($null -eq $configList) { $configList = @()}
                            $configList = $configList | sort
                            
                            if ($configList -ne @()) {
                                if (Compare-Object -ReferenceObject $configList -DifferenceObject $currList) {
                                    Write-Verbose -Message "Chef run list does not match"
                            
                                    # Set run list
                                    $list = ''
                                    $runlist | ForEach-Object {
                                        $propName = $_ | Get-Member -Type NoteProperty
                                        $value = $_."$($propName.Name)"
                                        $list += "$($propName.Name)[$value],"
                                    }
                                    $list = $list.TrimEnd(',')
                                    $list = "'$list'"
                                    Write-Verbose -Message "Assigning run list: $fqdnlower $($list | Format-List | Out-String)"
                                    Start-Process -FilePath 'knife' -ArgumentList "node run_list set $fqdnlower $list" -NoNewWindow -Wait
                                }
                            }
                        }
                    }

                     # Verify environment
                    if ($provOptions.environment) {
                        if ($node.chef_environment.ToLower() -ne $provOptions.environment.ToLower()) {
                            Write-Verbose -Message "Assigning Chef environment: $($provOptions.environment)"
                            Start-Process -FilePath 'knife' -ArgumentList "node environment set $fqdnlower $($provOptions.environment.ToLower())" -NoNewWindow -Wait
                        }
                    }

                    # Assign attributes if needed
                    if ($provOptions.attributes) {
                        $attribCmds = @()
                        $provOptions.attributes | Get-Member -Type NoteProperty | Foreach-Object {
                            $attrName = $_.Name
                            $attrValue = $provOptions.attributes."$($attrName)"
                            
                            # Construct our cmd to set the attribute with the JSON data
                            $cmd = 'exec -E "nodes.find(:name => ''' + $fqdnlower + "')"
                            $cmd += " {|n| n.normal_attrs.$attrName = "

                            if ($attrValue -is [string]) {
                                $cmd += "'" + $attrValue + "'"
                            } else {
                                $cmd += $attrValue
                            }
                            $cmd += '; n.save; }"'
                            $attribCmds += $cmd
                        }
                        if ($attribCmds.Count -gt 0) {
                            Write-Verbose -Message "Assigning Chef attributes: $($provOptions.attributes | Format-List -Property * | Out-String)"
                            $attribCmds | ForEach-Object {
                                Write-Verbose -Message "knife $_"
                                Start-Process -FilePath 'knife' -ArgumentList $_ -NoNewWindow -Wait
                            }
                        }
                    }
                } catch {
                    Write-Error -Message 'There was a problem running the Chef provisioner'
                    Write-Error -Message "$($_.InvocationInfo.ScriptName)($($_.InvocationInfo.ScriptLineNumber)): $($_.InvocationInfo.Line)"
                    write-Error $_
                }
            }

            $params = @{
                ComputerName = $ip
                Credential = $Options.GuestCredentials
                ScriptBlock = $cmd
                ArgumentList = @($Options, $chefOptions)
            }
            Invoke-Command @params
        } else {
           Write-Error -Message 'No valid IP address returned from VM view. Can not configure the Chef client'
        }
    } catch {
        Write-Error -Message 'There was a problem running the Chef provisioner'
        Write-Error -Message "$($_.InvocationInfo.ScriptName)($($_.InvocationInfo.ScriptLineNumber)): $($_.InvocationInfo.Line)"
        write-Error $_
        return $false
    }
}

end {
    Write-Debug -Message 'Chef provisioner: ending'
}