├── LICENSE ├── async.sh ├── esxidown.sh └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 NoJokeIT.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /async.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ############### 4 | ## VARIABLES ## 5 | ############### 6 | # Enter the absolute path to the directory in which esxidown.sh resides, without the trailing slash. Defaults to the folder of async.sh. 7 | SCRIPTPATH="" 8 | # Enter the absolute path to the directory in which esxidown.log should reside, without the trailing slash. Defaults to the folder of async.sh. 9 | LOGPATH="" 10 | # Can be used for testing, when set to 1 no actual shutdown commands are being issued. 11 | TEST=0 12 | # Number of times to wait for a VM to shutdown cleanly before forcing power off. (Default is 10, minimum is 1) 13 | WAITTRYS=10 14 | # How long to wait in seconds each try for a VM to shutdown. (Default is 15, minimum is 5) 15 | WAITTIME=15 16 | 17 | ################################### 18 | ## Do not change anything below! ## 19 | ################################### 20 | # Get current location of this script 21 | SCRIPTLOC=$(dirname "$(readlink -f "$0")") 22 | # Set defaults, if variables are empty or invalid 23 | SCRIPTPATH=${SCRIPTPATH:-$SCRIPTLOC} 24 | if [ ! -x "$SCRIPTPATH/esxidown.sh" ]; then 25 | echo "Path to script is invalid or script is not executable. Exiting." 26 | exit 1 27 | fi 28 | LOGPATH=${LOGPATH:-$SCRIPTLOC} 29 | if [ ! -d "$LOGPATH" ]; then 30 | echo "Path to logfile is invalid, defaulting to directory of this script" 31 | LOGPATH=$SCRIPTLOC 32 | fi 33 | TEST=${TEST:-0} 34 | if [ ! "$TEST" -eq 0 ] && [ ! "$TEST" -eq 1 ] 2>/dev/null; then 35 | echo "Couldn't determine if this is a test, assuming no." 36 | TEST=0 37 | fi 38 | WAITTRYS=${WAITTRYS:-10} 39 | if [ ! "$WAITTRYS" -ge 1 ] 2>/dev/null; then 40 | WAITTRYS=10 41 | fi 42 | WAITTIME=${WAITTIME:-15} 43 | if [ ! "$WAITTIME" -ge 5 ] 2>/dev/null; then 44 | WAITTIME=10 45 | fi 46 | 47 | # Executes esxidown.sh and passes all variables to it 48 | nohup /bin/sh "$SCRIPTPATH/esxidown.sh" "$LOGPATH" $TEST $WAITTRYS $WAITTIME > /dev/null 2>&1 & 49 | -------------------------------------------------------------------------------- /esxidown.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ESXi host automated shutdown script (https://github.com/ThisIsTenou/esxidown) 3 | 4 | ################################## 5 | ## !!! DO NOT EDIT THIS FILE!!! ## 6 | ################################## 7 | 8 | # Get variables from arguments 9 | LOGFILE="$1/esxidown.log" 10 | TEST=$2 11 | WAITTRYS=$3 12 | WAITTIME=$4 13 | 14 | exec 2>>"$LOGFILE" 15 | message () { 16 | echo "$(date '+%F %H:%M:%S') [esxidown] $1">>"$LOGFILE" 17 | } 18 | message "esxidown started" 19 | message "Testing enabled (0=No, 1=Yes): $TEST" 20 | 21 | # Gather all VM-IDs on the host 22 | SERVERIDS=$(vim-cmd vmsvc/getallvms | sed -e '1d' -e 's/ \[.*$//' | awk '$1 ~ /^[0-9]+$/ {print $1}') 23 | 24 | validate_shutdown() 25 | { 26 | vim-cmd vmsvc/power.getstate $SRVID | grep -i "off" > /dev/null 2<&1 27 | STATUS=$? 28 | 29 | if [ $STATUS -ne 0 ]; then 30 | if [ $TRY -lt $WAITTRYS ]; then 31 | # if the vm is not off, wait for it to shut down 32 | TRY=$((TRY + 1)) 33 | message "Waiting for guest VM ID $SRVID to shutdown (attempt #$TRY)..." 34 | sleep $WAITTIME 35 | validate_shutdown 36 | else 37 | # force power off and wait a little (you could use vmsvc/power.suspend here instead) 38 | message "Unable to gracefully shutdown guest VM ID $SRVID, forcing power off" 39 | if [ $TEST -eq 0 ]; then 40 | vim-cmd vmsvc/power.off $SRVID 41 | fi 42 | sleep $WAITTIME 43 | fi 44 | fi 45 | } 46 | 47 | # enter maintenance mode immediately 48 | message "Entering maintenance mode" 49 | if [ $TEST -eq 0 ]; then 50 | esxcli system maintenanceMode set -e true -t 0 & 51 | fi 52 | 53 | #send all shutdown messages 54 | for SRVID in $SERVERIDS 55 | do 56 | vim-cmd vmsvc/power.getstate $SRVID | grep -i "off\|Suspended" > /dev/null 2<&1 57 | STATUS=$? 58 | if [ $STATUS -ne 0 ]; then 59 | if [ $TEST -eq 0 ]; then 60 | vim-cmd vmsvc/power.shutdown $SRVID 61 | fi 62 | fi 63 | done 64 | 65 | 66 | for SRVID in $SERVERIDS 67 | do 68 | TRY=0 69 | 70 | vim-cmd vmsvc/power.getstate $SRVID | grep -i "off\|Suspended" > /dev/null 2<&1 71 | STATUS=$? 72 | 73 | if [ $STATUS -ne 0 ]; then 74 | message "Checking shutdown of guest VM ID $SRVID..." 75 | validate_shutdown 76 | else 77 | message "Guest VM ID $SRVID is off" 78 | fi 79 | done 80 | 81 | # guest vm shutdown complete 82 | message "Guest VM shutdown complete" 83 | 84 | # shutdown the ESXi host 85 | message "Shutting down ESXi host after 15 seconds" 86 | if [ $TEST -eq 0 ]; then 87 | esxcli system shutdown poweroff -d 10 -r "Automated ESXi host shutdown // esxidown.sh" 88 | fi 89 | 90 | 91 | 92 | 93 | # exit maintenance mode immediately before server has a chance to shutdown/power off 94 | # NOTE: it is possible for this to fail, leaving the server in maintenance mode on reboot! 95 | message "Exiting maintenance mode" 96 | if [ $TEST -eq 0 ]; then 97 | esxcli system maintenanceMode set -e false -t 0 98 | fi 99 | message "Script finished" 100 | 101 | # exit the session 102 | exit 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ESXi Auto Shutdown Script 2 | ------------------------- 3 | Tested on ESXi 7.0.1 (Enterprise Plus). 4 | This does NOT currently work on ESXI 8+, as described in [Issue #1](https://github.com/ThisIsTenou/esxidown/issues/1). A fix is currently being worked on. 5 | 6 | Based on the work of [Jon Saltzman](https://github.com/sixdimensionalarray/esxidown), [Andriy Babak](https://github.com/ababak/esxidown) and [Sophware](https://github.com/sophware/esxidown). 7 | 8 | 9 | **How does this script differ from the numerous, [other forks](https://github.com/sixdimensionalarray/esxidown/network/members)?** 10 | ------------------------- 11 | * VM shutdown commands are run in parallel, greatly reducing the total time needed until the host can shut down. 12 | * Command outputs are written to a log file 13 | * Script- and log-locations can be hardcoded or just left blank - they'll default to where async.sh resides. 14 | 15 | **What are the different files for?** 16 | ------------------------- 17 | The only interesting files are the two scripts, "esxidown.sh" and "async.sh". 18 | 19 | **esxidown.sh** is the script doing all the magic. When called, it will put your ESXi-Host into maintenance mode, send a shutdown command to all running VMs, wait for them to shutdown and then proceed to shutdown the phyiscal host, leaving maintenance mode again in the meantime. 20 | The "leaving maintenance mode"-part can technically fail, but at least in my experience, it's always been working fine and allows the VMs to autostart after the host has been brought up again. 21 | 22 | **async.sh** is a small wrapper, which tells the esxidown.sh-script everything it needs, like custom variables. Additionally, it executes esxidown.sh asynchronously (hence the name) in the background instead of the current shell. This means that you can leave the (ssh-)session, without having to wait for the script to finish first. All output is being sent to the logfile, not the console. 23 | 24 | **How do I use it?** 25 | ------------------------- 26 | 1. Copy both "esxidown.sh" and "async.sh" onto the **datastore** of your ESXi hosts. You can either do this through vCenter's WebUI or via SFTP/SSH. 27 | 2. Open up the **async.sh**-script in VI and edit the variables to match your environment. If no script-/log-path is set, it'll default to the directory in which async.sh resides. 28 | 3. Make both scripts executable: `chmod +x esxidown.sh async.sh` 29 | 4. Congrats! You can now safely shutdown your ESXi host just by calling the async.sh-script. 30 | 31 | **Important notes:** 32 | * Make sure that both paths are the absolute paths, starting at root level. Also make sure that all subsequent directories for the log location exist, otherwise it'll fail to be created there and default to where async.sh resides. 33 | * **STORE THE FILES ON A DATASTORE**. Like, honestly. They gotta be on a datastore. If you just throw them into the root-directory, they will **NOT** survive a reboot. This applies to both the scripts and the logfile. A log doesn't help you if it's gone seconds after ;) 34 | 35 | 36 | **I want to automate this to run when a power loss occurs. Is that possible?** 37 | ------------------------- 38 | Certainly! In my setup, my monitoring software is calling these scripts via ssh on all hosts. Here are some command snippets you can base your automation on: 39 | 40 | **Interactive oneliner:** 41 | ``` 42 | ssh root@esxi.lab.local '/bin/sh /vmfs/volumes/yourdatastore/async.sh' 43 | ``` 44 | 45 | **Non-interactive oneliner using authentication keys:** 46 | ``` 47 | ssh -i ./.ssh/my-key.pem root@esxi.lab.local '/bin/sh /vmfs/volumes/yourdatastore/async.sh' 48 | ``` 49 | 50 | **Non-Interactive expect-script using passwords:** 51 | Has to be called like this: `/usr/bin/expect script.exp esxi.lab.local pa$sw0rd` 52 | ``` 53 | #!/usr/bin/expect 54 | set host [lindex $argv 0] 55 | set password [lindex $argv 1] 56 | 57 | spawn ssh root@$host 58 | expect { 59 | "Are you sure you want to continue connecting" {send "yes\r";exp_continue} 60 | "Password:" {send "$password\r";exp_continue} 61 | } 62 | expect ":~]" 63 | send "/bin/sh /vmfs/volumes/yourdatastore/async.sh" 64 | send "\r" 65 | expect ":~]" 66 | send "exit\r" 67 | expect eof 68 | ``` 69 | 70 | --------------------------------------------------------------------------------