packer-cn/builder/hyperv/common/powershell/powershell.go

372 lines
7.6 KiB
Go

package powershell
import (
"bytes"
"fmt"
"io"
"log"
"os"
"os/exec"
"strconv"
"strings"
"github.com/hashicorp/packer-plugin-sdk/tmp"
)
const (
powerShellFalse = "False"
powerShellTrue = "True"
)
func IsTrue(s string) bool {
return strings.TrimSpace(s) == powerShellTrue
}
func IsFalse(s string) bool {
return strings.TrimSpace(s) == powerShellFalse
}
type PowerShellCmd struct {
Stdout io.Writer
Stderr io.Writer
}
func (ps *PowerShellCmd) Run(fileContents string, params ...string) error {
_, err := ps.Output(fileContents, params...)
return err
}
// Output runs the PowerShell command and returns its standard output.
func (ps *PowerShellCmd) Output(fileContents string, params ...string) (string, error) {
path, err := ps.getPowerShellPath()
if err != nil {
return "", fmt.Errorf("Cannot find PowerShell in the path")
}
filename, err := saveScript(fileContents)
if err != nil {
return "", err
}
debug := os.Getenv("PACKER_POWERSHELL_DEBUG") != ""
verbose := debug || os.Getenv("PACKER_POWERSHELL_VERBOSE") != ""
if !debug {
defer os.Remove(filename)
}
args := createArgs(filename, params...)
if verbose {
log.Printf("Run: %s %s", path, args)
}
var stdout, stderr bytes.Buffer
command := exec.Command(path, args...)
command.Stdout = &stdout
command.Stderr = &stderr
err = command.Run()
if ps.Stdout != nil {
stdout.WriteTo(ps.Stdout)
}
if ps.Stderr != nil {
stderr.WriteTo(ps.Stderr)
}
stderrString := strings.TrimSpace(stderr.String())
if _, ok := err.(*exec.ExitError); ok {
err = fmt.Errorf("PowerShell error: %s", stderrString)
}
if len(stderrString) > 0 {
err = fmt.Errorf("PowerShell error: %s", stderrString)
}
stdoutString := strings.TrimSpace(stdout.String())
if verbose && stdoutString != "" {
log.Printf("stdout: %s", stdoutString)
}
// only write the stderr string if verbose because
// the error string will already be in the err return value.
if verbose && stderrString != "" {
log.Printf("stderr: %s", stderrString)
}
return stdoutString, err
}
func IsPowershellAvailable() (bool, string, error) {
path, err := exec.LookPath("powershell")
if err != nil {
return false, "", err
} else {
return true, path, err
}
}
func (ps *PowerShellCmd) getPowerShellPath() (string, error) {
powershellAvailable, path, err := IsPowershellAvailable()
if err != nil {
log.Fatalf("IsPowershellAvailable: %v", err)
}
if !powershellAvailable {
log.Fatal("Cannot find PowerShell in the path")
return "", err
}
return path, nil
}
func saveScript(fileContents string) (string, error) {
file, err := tmp.File("powershell")
if err != nil {
return "", err
}
_, err = file.Write([]byte(fileContents))
if err != nil {
return "", err
}
err = file.Close()
if err != nil {
return "", err
}
newFilename := file.Name() + ".ps1"
err = os.Rename(file.Name(), newFilename)
if err != nil {
return "", err
}
return newFilename, nil
}
func createArgs(filename string, params ...string) []string {
args := make([]string, len(params)+5)
args[0] = "-ExecutionPolicy"
args[1] = "Bypass"
args[2] = "-NoProfile"
args[3] = "-File"
args[4] = filename
for key, value := range params {
args[key+5] = value
}
return args
}
func GetHostAvailableMemory() float64 {
var script = "(Get-WmiObject Win32_OperatingSystem).FreePhysicalMemory / 1024"
var ps PowerShellCmd
output, _ := ps.Output(script)
freeMB, _ := strconv.ParseFloat(output, 64)
return freeMB
}
func GetHostName(ip string) (string, error) {
var script = `
param([string]$ip)
try {
$HostName = [System.Net.Dns]::GetHostEntry($ip).HostName
if ($HostName -ne $null) {
$HostName = $HostName.Split('.')[0]
}
$HostName
} catch { }
`
//
var ps PowerShellCmd
cmdOut, err := ps.Output(script, ip)
if err != nil {
return "", err
}
return cmdOut, nil
}
func IsCurrentUserAnAdministrator() (bool, error) {
var script = `
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$principal = new-object System.Security.Principal.WindowsPrincipal($identity)
$administratorRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator
return $principal.IsInRole($administratorRole)
`
var ps PowerShellCmd
cmdOut, err := ps.Output(script)
if err != nil {
return false, err
}
res := strings.TrimSpace(cmdOut)
return res == powerShellTrue, nil
}
func ModuleExists(moduleName string) (bool, error) {
var script = `
param([string]$moduleName)
(Get-Module -Name $moduleName) -ne $null
`
var ps PowerShellCmd
cmdOut, err := ps.Output(script)
if err != nil {
return false, err
}
res := strings.TrimSpace(cmdOut)
if res == powerShellFalse {
err := fmt.Errorf("PowerShell %s module is not loaded. Make sure %s feature is on.", moduleName, moduleName)
return false, err
}
return true, nil
}
func HasVirtualMachineVirtualizationExtensions() (bool, error) {
var script = `
(GET-Command Hyper-V\Set-VMProcessor).parameters.keys -contains "ExposeVirtualizationExtensions"
`
var ps PowerShellCmd
cmdOut, err := ps.Output(script)
if err != nil {
return false, err
}
var hasVirtualMachineVirtualizationExtensions = strings.TrimSpace(cmdOut) == "True"
return hasVirtualMachineVirtualizationExtensions, err
}
func DoesVirtualMachineExist(vmName string) (bool, error) {
var script = `
param([string]$vmName)
return (Hyper-V\Get-VM -Name $vmName | ?{$_.Name -eq $vmName}) -ne $null
`
var ps PowerShellCmd
cmdOut, err := ps.Output(script, vmName)
if err != nil {
return false, err
}
var exists = strings.TrimSpace(cmdOut) == "True"
return exists, err
}
func DoesVirtualMachineSnapshotExist(vmName string, snapshotName string) (bool, error) {
var script = `
param([string]$vmName, [string]$snapshotName)
return (Hyper-V\Get-VMSnapshot -VMName $vmName | ?{$_.Name -eq $snapshotName}) -ne $null
`
var ps PowerShellCmd
cmdOut, err := ps.Output(script, vmName, snapshotName)
if err != nil {
return false, err
}
var exists = strings.TrimSpace(cmdOut) == "True"
return exists, err
}
func IsVirtualMachineOn(vmName string) (bool, error) {
var script = `
param([string]$vmName)
$vm = Hyper-V\Get-VM -Name $vmName -ErrorAction SilentlyContinue
$vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running
`
var ps PowerShellCmd
cmdOut, err := ps.Output(script, vmName)
if err != nil {
return false, err
}
var isRunning = strings.TrimSpace(cmdOut) == "True"
return isRunning, err
}
func GetVirtualMachineGeneration(vmName string) (uint, error) {
var script = `
param([string]$vmName)
$generation = Hyper-V\Get-Vm -Name $vmName | %{$_.Generation}
if (!$generation){
$generation = 1
}
return $generation
`
var ps PowerShellCmd
cmdOut, err := ps.Output(script, vmName)
if err != nil {
return 0, err
}
generationUint32, err := strconv.ParseUint(strings.TrimSpace(string(cmdOut)), 10, 32)
if err != nil {
return 0, err
}
generation := uint(generationUint32)
return generation, err
}
func SetUnattendedProductKey(path string, productKey string) error {
var script = `
param([string]$path,[string]$productKey)
$unattend = [xml](Get-Content -Path $path)
$ns = @{ un = 'urn:schemas-microsoft-com:unattend' }
$setupNode = $unattend |
Select-Xml -XPath '//un:settings[@pass = "specialize"]/un:component[@name = "Microsoft-Windows-Shell-Setup"]' -Namespace $ns |
Select-Object -ExpandProperty Node
$productKeyNode = $setupNode |
Select-Xml -XPath '//un:ProductKey' -Namespace $ns |
Select-Object -ExpandProperty Node
if ($productKeyNode -eq $null) {
$productKeyNode = $unattend.CreateElement('ProductKey', $ns.un)
[Void]$setupNode.AppendChild($productKeyNode)
}
$productKeyNode.InnerText = $productKey
$unattend.Save($path)
`
var ps PowerShellCmd
err := ps.Run(script, path, productKey)
return err
}