invoke-command instead of invoke-expression

In my previous post (which you can find here ) I use the invoke-expression cmdlet for running a Powershell script which was downloaded with invoke-webrequest.
And this was a good solution. The code that was downloaded and executed was a powershell script that would run a private function. This private function then was formated with the three scriptblocks Begin,End and Process.
Parameters where with a same construct being downloaded from a git repository and placed in a powershell Object called $P.
With this approach I separated code parameters from the actual code.
Using GIT I was able to versioning my parameters file, separate from my script code. This setup is working great. And it gives flexibility by leaving the code untouched when changing parameters.

But…

Yeah, a but…. I still needed a way to pass parameters /arguments on the command line. Using invoke-expression… well that wasn’t possible.
So I looked into invoke-command, which has an -argumentlist parameter., making it possible to pass one or more arguments to the script. Using named parameters isn’t possible, which is not what I was looking for.
So to support naming parameters, I decided to introduce just one parameter. And this parameter should be a JSON string, making it possible to pass multiple parameters merged into a JSON object.

The only challenge with this is that all the interpreters that the code was going to pass, should leave the JSON string intact, including the quotes. And I didn’t want to escape any quotes. That would be messy and prone to errors. But encoding it, should solve this issue. The argument is a base64 encoded JSON string.

Is it secure?

Well, No …. not at all… it is a base64 encoding. My goal was not to make it more secure, but that the string wouldn’t be changed by the different shell interpreters.
Off course, you can make it more secure by using private/public key-pairs. You could use a docker volume containing the encoding keys, or other secure methods. When using base64 coding, just don’t pass any sensitive data (passwords) with it. There are other, more secure, approaches for this with containers.

parameter approaches

This setup gives me different approaches to pass parameters to the script. The more static parameters are stored in a .json file, stored in a GIT repository.
And the more dynamic parameters (like VM names to start), are passed via the base64 encoded JSON string.

What changed ?

I changed to following items:

  • changed entrypoint string
    • using invoke-command instead of invoke-expression
    • placing invoke-webrequest inside a scriptblock
    • using argumentlist to pass a base64 string, encoded a JSON string
  • changed powershell wrapper script to decode inpu

Docker Entrypoint

The previous docker entrypoint was something like

pwsh -Command invoke-expression '$(Invoke-WebRequest -SkipCertificateCheck -uri ' + <git URI> + ' -Headers @{"Cache-Control"="no-store"} )'

The new entrypoint is looking like

pwsh -Command invoke-command -scriptblock ([scriptblock]::Create( (Invoke-WebRequest -SkipCertificateCheck -uri <git URI> -Headers @{"Cache-Control"="no-store"} ).content ) ) -ArgumentList <base64 coded JSON string>

As you can see, the one-liner has grown.
I used the -scriptblock and the -ArgumentList parameter from the invoke-command. The -scriptblock contains the Invoke-webrequest cmdlet which downloads the RAW version of the powershell script on the GIT repository.
The invoke-command cmdlet then executes this scriptblock and passing the argument from the argumentlist to this script.

Script Layer

The script has a wrapper layer, a main layer (containing the Begin,End and Process blocks) and the Process block containing the specific code to run.

<#
.SYNOPSIS
    template.ps1 powershell
.PARAMETER inputObject 
    A JSON string base64 (UTF-8) encoded
#>

param(
    [string][Parameter(
        ValueFromPipeline = $true, 
        ValueFromPipelineByPropertyName = $true,
        HelpMessage="JSON string base64 (UTF-8) encoded.")]$inputObject=""
)

function main {

}
#-- calling the real powershell code to run
main

main layer (function)

I choose to use the function method to preserve my code format structure. For most of my powershell code I use the End,Begin and Process scriptblocks to structure the code. And I didn’t want to stepp down from that approach.

function main {
    <#
    .SYNOPSIS

    #>
    Begin{
        $uri = <url to RAW version of parameter file>
        #-- trying to load parameters into $P object, preferably json style
        try { $webResult= Invoke-WebRequest -SkipCertificateCheck  -Uri ($scriptrootURI+$scriptName+".json") -Headers @{"Cache-Control"="no-store"}  }
        catch  {
            write-host "uri : " + $scriptrootURI
            throw "Request failed for loading parameters.json with uri: " + $webResult 
        }
        # validate answer
        if ($webResult.StatusCode -match "^2\d{2}" ) {
            # statuscode is 2.. so convert content into object $P
            $P = $webResult.content | ConvertFrom-Json 
        } else {
            throw ("Failed to load parameter.json from repository. Got statuscode "+ $webRequest.statusCode)
        }

    #-- private functions
        function exit-script {
            ...
        }

    #--- proces inputObject (the argument passed via the cmd line
        # decode that inputObject as UTF-8 base64 and convert it to a powershell object
        $A= ConvertFrom-Json -InputObject ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($($inputObject)))) -ErrorAction SilentlyContinue -ErrorVariable err1
        if ($err1) {
            writ-host "Failed to proces input object"
            exit-script
        }
    }
    End {
        exit-script -exitcode 0
    }
    Process {
        #-- the code that is doing the real work
        write-host ($P.world) #-- from the parameter.json file
        write-host ($A.universe) #-- passed via the cmd line argument
    }
}

Final

So I hope this blog gives you some ideas with you code challenges.
I’m going to write a more structured set of articles , a deep dive into my FaaS – like setup. So keep following this blog, when interested.
And comments, are always welcome.

Dynamicly executing powershell code from GIT – a FaaS way

In this post I mentioned that I was tipping my toes in vRA.
And…. yeah, it is not dipping anymore, it is a deep dive… 🙂 but that is for another blog

This blog is about a challenge I solved by using a FaaS approach. FaaS stands for Function as a Service.
I have a small UPS running for my home environment. And found out that it could support my home environment for about 30 min. And then poof… power off…. So I was searching for a solution that my vsphere environment would be shutdown, triggered from a home assistant /Node-Red automation flow, monitoring my UPS status.

My first way of thinking was to use the REST API of vSphere / VMware esxi. But not all the actions I need are published. (Like shutdown of an ESXi host.)
And I want to be dynamic as possible.
I want to shutdown / suspend all VMs expect VMs that are tagged as coreVM. So the code that I should write, doesn’t contain the VM names or IDs, but would filter out all VMs that have a vSphere tag with coreVM.

The Idea

As mentioned, is that the automation would find all VMs that need to be suspended or shutdown ( depending if VMtools is running). And VMs that have a vSphere tag UPS/coreVM, would be ignored.

These VMs are controlled with the startup/shutdown feature of my ESXi host.
The total automation flow would be a 3 fase flow. Fase 1 shutdown all but the core VMs. Fase 2 shutdown the ESXi host, which would automaticly shutdown VMs controlled via the startup/shutdown feature. Fase 3, shutdown of Synology NAS and Home Assistant
The core VMs are my vCenter, Log Insight and router VM.

The tools

The tools I use are:

  • Home Assistant with
    • NUT intergration (monitoring my UPS)
    • Node-Red flow automation (ussing HTTP REST API calls to control docker via Portainer)
  • Docker CE with Portainer CE running on a synology NAS
    • Docker image vmware/powerCLI
    • Portainer controlling the docker environment and leveraging control via its APIs
  • Gitea GIT server running locally (containing my script and parameter files)

the FaaS way

For fase 2 I try to use a FaaS approach. Meaning I have a function for shutting down / suspending VMs. This function is run in a temporary runtime environment (docker container) and only available at runtime. The function is not part of the runtime environment, but on runtime it is downloaded from a GIT repository and executed.
This gives the advantage of maintaining a runtime environment seperate from scripts (or the other way around). And on every execution, it starts with a clean environment.
For the function I use powerCLI, because I haven’t found an API call in vSphere 7.0 that will shutdown an ESXi host. And filtering VMs on vsphere tags was (for me) a bridge to far.

Using a container gives me also the possibility to seperate my vSphere credentials from my script, by saving it in a docker volume, which is only mounted at runtime. The function itselfs contains no credentials. It looks to a file in that docker volume.

The hurdles I encountered were:

  • how to run the script from a git repo
    • by using a powershell one-line invoke-expression with invoke-webrequest using the url to the raw presentation of a file (script) in the git repo.
    • using this one-liner as the entrypoint parameter when creating the container
  • how to bypass caching by the web browser
    • using “Cache-Control”=”no-store” in the header
  • running a full powershell script via invoke-expression
    • write the full code block as a powershell function (with begin, End and Process blocks) and call that function within the script.

The powershell one-line is

pwsh -Command invoke-expression '$(Invoke-WebRequest -SkipCertificateCheck -uri ' + <git URI> + ' -Headers @{"Cache-Control"="no-store"} )'

The <git URI> is the url to the raw representation of the script file in the GIT repo. When leveraging the portainer/docker api to create the container you need to use the JSON notation for the entrypoint. The one-line will look something like:

["pwsh",
"-Command",
"invoke-expression",
'$(Invoke-WebRequest -SkipCertificateCheck -uri ' + <git URI> + ' -Headers @{"Cache-Control"="no-store"} )']

Powershell script formant / the snag

The snag with using invoke-expression is that it can’t handle a powershell script that has a Begin, End and Process code block. While I this is how I write my code, and wouldn’t like to deviate from it, it meant I had a snag.
The solution was to write a powershel script that contains a private function and execution of that function, like this

function main {
    <#
    .SYNOPSIS
        Example code for running full script from an URI.
    .DESCRIPTION
        Example code to run a powershell script with dynamic blocks Begin,Process and End.
        Loading parameters from a JSON file into an P object.
        By wrapping the code into the function main, we can use begin, process and end scriptblocks when calling with Invoke-Expression
        The Process block contains the main code to execute.
        The Begin and en blocks are mainly used for setting up the environment and closing it.
    .EXAMPLE
    Run the following cmdline in a powershell session.
    Invoke-Expression (Invoke-webrequest <URL>).content
    .NOTES    
    #>
    Begin {
        #=== Script parameters

        #-- GIT repository parameters for loading the parameters.json
        $scriptName="startVMs"
        $scriptGitServer = "https://....."   # IPv4 or FQDN of GIT server
        $scriptGitRepository = "organisation/repo/" # uri part containing git repository
        $scriptBranch = "master/" # GIT branch
        $scriptrootURI = $scriptGitServer+$scriptGitRepository+"raw/branch/"+$scriptBranch

        #==== No editing beyond this point !! ====
        $ts_start=get-date #-- Save current time for performance measurement

        #--- write log header to console
        write-host
        write-host "================================================================="
        write-host ""
        write-host "script: $scriptName.ps1"
        write-host ""
        write-host "-----------------------------------------------------------------"

        #-- trying to load parameters into $P object, preferably json style
        try { $webResult= Invoke-WebRequest -SkipCertificateCheck  -Uri ($scriptrootURI+$scriptName+".json") -Headers @{"Cache-Control"="no-store"}  }
        catch  {
            write-host "uri : " + $scriptrootURI
            throw "Request failed for loading parameters.json with uri: " + $webResult 
        }
        # validate answer
        if ($webResult.StatusCode -match "^2\d{2}" ) {
            # statuscode is 2.. so convert content into object $P
            $P = $webResult.content | ConvertFrom-Json 
        } else {
            throw ("Failed to load parameter.json from repository. Got statuscode "+ $webRequest.statusCode)
        }
    }
    End {

    }
    Process {

    }
}
main

What it does, is the script will run the function main. That function is basicly the full powershell script. In the begin code block the invoke-Webrequest is used to load a .json file and convert it to a powershell object called P.
This object contains all parameters used in the rest of script (like vCenter FQDN).

Result

The result is that from a monitoring trigger Node-Red will do some REST API calls to portainer to create run and delete a docker container based on a vmware/powerCLI image. During the lifetime of this container a volume is mounted with authorisation information and a powershell one-liner is executed which runs a powershell code directly loaded from a GIT repository.
With this setup I can run on demand any powershell script which doesn’t need user interaction, maintained in a GIT repository.

I hope you enjoyed this post. do you have questions / comments, please leave them below or reach out to me on twitter

Set Email notificions on vCenter Alarm definitions

One of the task when configuring a vCenter is to setup the alarm notifications. But to configure 268 alarms by hand…. well it is just a scary task when you do it via the gui / webclient.
In short I created this script that you can find on my github.

The full story

Continue reading “Set Email notificions on vCenter Alarm definitions”

Issue 2 – bypassing the fingerprint cache message when using PLINK

This article is part of a series of articles about issues I encountered during implementation of a vSphere stretched cluster based on vSphere 6.7 U1.
You can find the introduction article here

Issue 2

For some configuration settings I need SSH access to the host.  I use plink.exe to execute instructions through the SSH session. One issue, the first time when you connect with plink you get a message about storing the fingerprint ID in the cache. Normally you would accept this when using putty. But now this is going to be a challenge.
On some other blogs I found the solution. You echo the ‘Y’ which results in storing the ID in the cache.
In my code I now  call plink two times. The first time to accept the fingerprint, the second time to execute the command.
Why two times ? Well, I can’t assume that the fingerprint ID is already known.
The first plink instruction is a simple exit, we only want to check if we can logon.

$credential=get-credential
$plink="d:\plink.exe $hostname -l "+ $credential.username + " -pw " + $credential.getnetworkcredential().password
$command="ls"
invoke-expression ("echo Y | " + $plink +  " -ssh exit")
invoke-expression ($plink + " "+ $command)

Issue 1 – changing root password

This article is part of a series of articles about issues I encountered during implementation of a vSphere stretched cluster based on vSphere 6.7 U1.
You can find the introduction article here

Issue

All the hosts are delivered with 6.5 U2 pre-installed, and they have their own root password. For the implementation we want to have just one general root account password. So after adding all the hosts to the cluster I want to change the root password with powercli. But I tripped over a bug in get-esxcli (thanks to this thread ). The ‘&’ character is not correctly being interpreted when using get-esxli.
The script I wrote checks if the new password contains that character and will kindly ask to change it. After succesfull validation of the password it will apply it to all selected esxi hosts.
I

#-- select one or more hosts
[array]$esxiHosts=get-vmhost | select name | sort | out-gridview -Title "Select one or more ESXi Hosts"-OutputMode Multiple
if ($esxiHosts.count -eq 0) {
write-host "No host(s) selected, will exit." -foregroundcolor yellow
exit
}
#-- ask for root password and validate it agains known bug
Do {
$newCredential = Get-Credential -Username root -Message "Enter the password for the ESXi root account."
$isValid=$true
if ($newCredential.getNetworkCredential().Password -imatch "[\&]") {
$isValid=$false
write-host"Password contains character & which get-esxcli can't handle (bug)..... please consider a different password." -foregroundcolor yellow
}
}
until ($isValid)

#-- change root password for all selected esxi hosts
foreach ($esxiHost in $esxiHosts) {
$esxiHost=get-vmhost -Name -$esxiHost.name
$esxiCli=get-esxcli -v2 -vmhost $esxiHost
$arguments=$esxcli.system.account.set.createArgs()
$arguments.id=$newCredential.UserName
$arguments.password=$newCredential.GetNetworkCredential().password
$arguments.passwordconfirmation=$arguments.password
try {$esxcli.system.account.set.Invoke($arguments)}
catch{write-host "Setting password failed for " $esxiHost.name -ForegroundColor Yellow}
}

Issues I encountered with a stretched cluster implementation on 6.7 U1

At the moment I’m busy with a stretched cluster implementation based on vSphere 6.7 U1. Most of the configuration is straight forward. But I encounter some snags.
So this post is about these snags, and how I solved them.

For configuring 16 hosts I use a lot of powerCLI. Why ? Well I have some issues with host profiles, and not the time (yet) to figure out what is going on.
Edit: I found out what the issue is, I’ll explain it in Issue 3.

I encountered the following issues

build-vmHostImage v0.2

The script has been modified to exclude certain vibs. The reason is that with some configurations we found out that the wrong vib was loaded on a vspherer host. Resulting in a PSOD, due to driver, hardware, ESXi version conflicts.

With this in mind, I added the functionality to exclude VIBs. The excluded VIBs are listed in the parameters.ps1 file.
After the new image profile has been configured, the script will exclude the VIBs, if they are present. VIBs that are excluded are reported in excluded.txt file.

The script creates a parameters.ps1 file in the project folder, containing the names of the Vibs that are excluded. This file is used to re-create an image if necessary.

You can find the script at github, here

Loading PowerCLI one-line

Finaly 🙂
You can load PowerCLI with a one-liner again.
Why ? well… PowerCLI 6.5 is only moduled based, while the previous versions where a mix off snappins and modules.
When it was mainly snappin bassed I used a one-liner like:

get-pssnapin -registered vmware* | add-pssnappin

When PowerCLI was module and snappin based, I used a custom PowerShell function import-PowerCLI (see my previous post https://vblog.bartlievers.nl/2016/11/22/import-powercli/ for details). But with PowerCLI 6.5 R1 you can use a one-line once again.
This one-line is :

> Get-Module -ListAvailable vmware* |Import-Module

import-PowerCLI

For my work I use PowerCLI a lot, but I don’t like the shell that is available when installing PowerCLI. I just want to start PowerShell (mostly through PowerShell ISE) and load with one command the PowerCLI modules and/or snapins and not be limited.

Before PowerCLI 6.5, PowerCLI was a mix of powershell snappins and modules. To support older PowerCLI versions, the script will check if there are vmware powershell snappins registered. And will load them.
Powershell modules will have precedence over the snappins. Continue reading “import-PowerCLI”

%d bloggers like this: