Wednesday, December 23, 2015

Cleaning Up from the Change Block Tracking (CBT) Bug 2137546

There's a couple of ways to resolve the potential CBT corruption bug - as noted at VMware ESXi 6.0, Patch ESXi600-201511401-BG: Updates esx-base (2137546).

First, you need to load the latest build onto your ESXi hosts located at VMware ESXi 6.0, Patch Release ESXi600-201511001 (2137545) on affected hosts.

After you update your ESXi hosts, you can find directions at Changed Block Tracking is reset after a storage vMotion operation in vSphere 5.x (2048201) to Reset CBT after you patch or by following an alternate method of resetting change block tracking by doing the following for each VM on a patched host:

  1. Disable CBT Tracking 
  2. Create a Snapshot - none can exist
  3. Remove the Snapshot
  4. Re-Enable CBT Tracking

Veeam has a nice script to resolve the issue - https://www.veeam.com/kb1113

The only thing lacking, for me, is change tracking for virtual machines that have already had the CBT reset. I'm not going to be able to patch all my hosts at once, but progress A.S.A.P - and not need to repeat resets unnecessarily.

For my backups, I've been kicking off a once a week disabled CBT backup on all my VMware jobs in Veeam Backup and Recovery with the following script: https://github.com/cajeeper/Veeam/blob/master/Run-JobsWithoutCBT.ps1

I wrote a script in PowerCLI - similar to Veeams - that resets the virtual machine's CBT tracking by following the mentioned change block tracking reset steps above, but I've added additional tagging to indicate prior CBT resets on VMs, so I'm not attempting to continuously repair them all and I have some change tracking for future reference.

You can find the latest version at: https://github.com/cajeeper/PowerCLI/blob/master/Reset-VM-CBT.ps1

<#  
 .SYNOPSIS  
  Script reset VMs CBT and tag them as being reset if ESXi600-201511001 has been loaded on its host.
    
 .DESCRIPTION
  CBT Bug VMWare KB 2137546 is no bueno. I wanted a way to patch VMs possibly affected
  and keep track of ones that were patched - so as I progress through and patch for
  this bug, I don't have to keep running the patch process on  VMs multiple time
  unnecessarily.
 
 .NOTES   
  Author   : Justin Bennett   
  Date     : 2015-12-23
  Contact  : http://www.allthingstechie.net
  Revision : v1.0
  Changes  : v1.0 Original
#>
#Connect-VIServer myvCenterServer.local

#Show Progress
$showProgress = $true

#Gather VM Hosts
$ESXHosts = Get-VMHost

#Gather VM Hosts that have corrected for CBT Bug build
$patchedESXHosts =  $ESXHosts | ? { $_.Build -ge 3247720 }

#Gather VMs from ESX hosts with corrected for CBT Bug build
$VMs = $patchedESXHosts | Get-VM

#Create tags if necessary
$nul = New-TagCategory PowerCLI -ErrorAction SilentlyContinue
$nul = New-Tag -Name ResetCBT -Category PowerCLI -Description "bit.ly/1U5nyxz" -ErrorAction SilentlyContinue

#Get the ResetCBT Tag
$Tag = Get-Tag ResetCBT

#VMs already patched
$existingResetCBTVMs = (Get-TagAssignment -Category "PowerCLI" | ? { $_.Tag.Name -eq $Tag.Name -and $_.Entity.Uid -like "*VirtualMachine=*"} | select Entity).Entity

#Gather VMs not already CBT Reset and ChangeTrackingEnabled is Enabled (No need to reset if CBT already disabled)
$resetCBTVMs = $VMs | ? { $_.Id -notin $existingResetCBTVMs.Id -and ($_.ExtensionData.Config).ChangeTrackingEnabled }
$resetCBTVMsCount = 0

if ($resetCBTVMs.Count -gt 0) { $resetCBTVMs | % {$i=0} {
  $i++
  if ($_.PowerState -eq "PoweredOn" -and ($_ | Get-View).snapshot -eq $null) {
   try {
    #Disable CBT Spec
    if($showProgress) { Write-Progress -Activity "Reset-VM CBT" -Status "$($i)/$($resetCBTVMs.Count): VM:$($_.Name) - Disabling CBT" -PercentComplete (($i/$resetCBTVMs.Count)*100) }
    $VMConf = New-Object VMware.Vim.VirtualMachineConfigSpec 
    $VMConf.ChangeTrackingEnabled = $false

    $_.ExtensionData.ReconfigVM($VMConf)

    #Creating snapshot
    if($showProgress) { Write-Progress -Activity "Reset-VM CBT" -Status "$($i)/$($resetCBTVMs.Count): VM:$($_.Name) - Creating Snapshot to Clear CBT" -PercentComplete (($i/$resetCBTVMs.Count)*100) }
    $snap=$_ | New-Snapshot -Name 'Clear CBT'
    
    #Removing snapshot
    if($showProgress) { Write-Progress -Activity "Reset-VM CBT" -Status "$($i)/$($resetCBTVMs.Count): VM:$($_.Name) - Removing Snapshot" -PercentComplete (($i/$resetCBTVMs.Count)*100) }
    $snap | Remove-Snapshot -confirm:$false
    
    #Enable CBT
    if($showProgress) { Write-Progress -Activity "Reset-VM CBT" -Status "$($i)/$($resetCBTVMs.Count): VM:$($_.Name) - Enabling CBT" -PercentComplete (($i/$resetCBTVMs.Count)*100) }
    $VMConf.ChangeTrackingEnabled = $true
    $_.ExtensionData.ReconfigVM($VMConf)
    
    #Tagging Reset CBT VM
    $nul = $_ | New-TagAssignment $Tag
    
    $resetCBTVMsCount++
   } catch { write-warning "Failed to reset CBT on VM: $($_.Name)" }
  } else { 
   if($_.PowerState -ne "PoweredOn") { write-warning "VM: $($_.Name) Not completed - Needs to be in PoweredOn state" }
   if(($_ | Get-View).snapshot -ne $null) { write-warning "VM: $($_.Name) Not completed - Needs to have no existing snapshots" }
  }
 }
} else { write-warning "No VMs to Reset CBT on" }

New-Object -TypeName PSCustomObject -Property ([ordered]@{
 ESXHosts = $ESXHosts.Count
 patchedESXHosts = $patchedESXHosts.Count
 VMCount = $VMs.Count
 existingResetCBTVMs = $existingResetCBTVMs.Count
 resetCBTVMs = $resetCBTVMs.Count
 CompletedCBTVMs = $resetCBTVMsCount
})
Formatted for web with http://codeformatter.blogspot.com/ 

Best of luck!

Script Running on Example VMs

Script Running on Example VMs

Script Finished running on Example VMs

vSphere Tags added to VMs with CBT Reset

vSphere Tag added to VM with CBT Reset

Tuesday, December 1, 2015

Remove Windows Update using PowerShell and KB

I would like a native cmdlet in PowerShell to remove Windows Updates.

Until that time comes, I've created my own function that takes the native comobjects and searches for the update I want to remove by KB ID.

Bon Appétit

Current version at https://github.com/cajeeper/PowerShell/blob/master/Remove-WindowsUpdate.ps1

<#  
 .SYNOPSIS  
  Remove One to Many Windows Updates
    
 .DESCRIPTION   
  Remove One to Many Windows Updates from OS.
    
 .NOTES   
  Author   : Justin Bennett   
  Date     : 2015-12-01  
  Contact  : http://www.allthingstechie.net
  Revision : v1  
 .EXAMPLE 
  C:\PS> #Uninstall One Update 
  C:\PS> Remove-WindowsUpdate 123456
  
 .EXAMPLE 
  C:\PS> #Uninstall Multiple Updates
  C:\PS> Remove-WindowsUpdate 123456,456123
#>
Function Remove-WindowsUpdate {
    [CmdletBinding()]  
      param (  
           [parameter(Mandatory=$True)] $RemoveKB
     )

 $Searcher = New-Object -ComObject Microsoft.Update.Searcher
 $RemoveCollection = New-Object -ComObject Microsoft.Update.UpdateColl

 #Gather All Installed Updates
 $SearchResult = $Searcher.Search("IsInstalled=1")

 #Add any of the specified KBs to the RemoveCollection
 $SearchResult.Updates | ? { $_.KBArticleIDs -in $RemoveKB } | % { $RemoveCollection.Add($_) }

 if ($RemoveCollection.Count -gt 0) {
  $Installer = New-Object -ComObject Microsoft.Update.Installer
  $Installer.Updates = $RemoveCollection
  $Installer.Uninstall()
 } else { Write-Warning "No matching Windows Updates found for:`n$($RemoveKB|Out-String)" }
}
Formatted for web with http://codeformatter.blogspot.com/ 


Wednesday, September 23, 2015

Installing ESXi - Operation Failed "Unable to read partition table for device"

In process of re-purposing an existing a Hyper-V host as a VMware ESXi host - no apologies, by the way - I've ran into this error a few times.


The installation process usually consists of re-sizing the original OS mirrored disk set virtual disks as one 32GB virtual disk and the remaining space as a secondary space to utilize as a utility datastore later for vSphere.



So, what's the reason for the this error? Well, the RAID controller didn't and wasn't told to zero out the disks during the changes to the virtual disks. The end result is the original partition table that was saved on the 278GB virtual disk is visible on the new 32GB virtual disk.

To fix this, we need to either create a new partition table or zero out the partition table data.

Two easy ways, is use dd or GParted on a linux distro live boot disk - Lubuntu is my first go-to.

With dd (to zero out the partition table data):
  1. Open a terminal window

  2. Run the command: dd if=/dev/zero of=/dev/sda bs=512 count=1
    • Note: you may need to run as sudo or elevate to su
With GParted (to create a new partition table):
  1. Open GParted

  2. Click Device, and select Create Partition Table...

  3. Click Apply

Monday, September 21, 2015

Disable Start Menu Win+X Hidden Context-Menu

I was hoping to find a GPO (Group Policy Object) setting to disable the hidden context menu for the Start Menu (a.k.a. Win+X) in Windows 8 / 8.1 / 2012 / 2012 R2. I did find the menu items are shortcuts under C:\Users\AUser\AppData\Local\Microsoft\Windows\WinX under Group1-3 folders. I was able to effectively disable the Win+X Menu by moving the Group 2 and Group 3 folders out - so my public terminal users can't have access to those shortcuts.

I still hope to be WOW'd by a GPO setting some day, but until then...

Here's a PowerShell script I launched on each of my Remote Desktop Sessions hosts to effectively disable the Win+X menu:
#kill start menu right-click context menu
$folder = "C:\Users\Default\AppData\Local\Microsoft\Windows"
mkdir $folder\WinX.org
Move-Item -Path $folder\WinX\group[2-3] -Destination $folder\WinX.org
Formatted for web with http://codeformatter.blogspot.com/ 

Before:





Ran the script:

After a log-off and back in (my user profiles are non-persistent):



Note: If you have existing persistent user profiles, you'll need to go through each profile and remove the Group# folder you're targeting. If you need to update all users, here's that script too - you're welcome.

$users = (Get-ChildItem C:\Users -Directory -Exclude "Public", "Administrator").FullName
foreach ($user in $users) {
 $folder = "$($user)\AppData\Local\Microsoft\Windows"
 mkdir $folder\WinX.org
 Move-Item -Path $folder\WinX\group[2-3] -Destination $folder\WinX.org
}
Formatted for web with http://codeformatter.blogspot.com/ 



Wednesday, August 19, 2015

Older Remote Desktop Clients Can't Connect - Requires Network Level Authentication Error

I noticed this morning that my older thin client terminals started getting errors connecting to our central remote desktop session hosts.

It appears update KB 3075220 - part of KB 3080348 - just rolled through my session hosts over night and turned on the Network Level Authentication requirement.

I disabled the requirement by unchecking the "Allow connections only from computers running desktop with network level authentication" and it appears I'm back in business. Thanks for the early morning fire drill Microsoft.

https://support.microsoft.com/en-us/kb/3080348
MS15-082: Vulnerabilities in RDP could allow remote code execution: August 11, 2015

https://technet.microsoft.com/library/security/MS15-082
Vulnerabilities in RDP Could Allow Remote Code Execution (3080348)

After KB 3080348 update loaded, the "Network Level Authentication" was checked

Unchecked the "Network Level Authentication" requirement

Monday, May 25, 2015

Home Water System Pt 3 - Sense that Arduino via SNMP

Hardware:
    Arduino compatible Uno R3
    Arduino compatiable Ethernet Shield W5100
    15 PSI pressure sensor
    USB Wall Charger
    Network Switch
    The USB Charger was laying around and everything else was easily found on a eBay.
     

Libraries: (My copies of the oringals are at https://github.com/cajeeper/Arduino)
  • Agentuino - https://github.com/1sw/Agentuino
    • Requires
      • MemoryFree - https://github.com/maniacbug/MemoryFree
        Streaming - http://arduiniana.org/libraries/streaming/


My Arduino Sketch - A modification of the example Agentuino sketch:
/**  
 * SNMP Sensor Input Sketch v.2
 * Justin Bennett http://justin-bennett-msjc.blogspot.com/
 *
 *
 * Adapted from the Agentuino SNMP Example Sketch:
 *    Agentuino SNMP Agent Library Prototyping...  
 *  
 *    Copyright 2010 Eric C. Gionet <lavco_eg@hotmail.com>  
 *  
 */  
 #include <Streaming.h>     // Include the Streaming library  
 #include <Ethernet.h>     // Include the Ethernet library  
 #include <SPI.h>  
 #include <MemoryFree.h>  
 #include <Agentuino.h>  
 //#include <Flash.h>  
 //  
 //  
 #define DEBUG  
 //  
 static byte mac[] = { 0xAA, 0xAA, 0xBB, 0xBB, 0x00, 0x01 };  
 static byte ip[] = { 192, 168, 0, 10 };  
 static byte gateway[] = { 192, 168, 0, 1 };  
 static byte subnet[] = { 255, 255, 255, 0 };  
 //  
 //  
 // tkmib - linux mib browser  
 //  
 // RFC1213-MIB OIDs  
 // .iso (.1)  
 // .iso.org (.1.3)  
 // .iso.org.dod (.1.3.6)  
 // .iso.org.dod.internet (.1.3.6.1)  
 // .iso.org.dod.internet.mgmt (.1.3.6.1.2)  
 // .iso.org.dod.internet.mgmt.mib-2 (.1.3.6.1.2.1)  
 // .iso.org.dod.internet.mgmt.mib-2.system (.1.3.6.1.2.1.1)  
 // .iso.org.dod.internet.mgmt.mib-2.system.sysDescr (.1.3.6.1.2.1.1.1)  
 const static char sysDescr[] PROGMEM   = "1.3.6.1.2.1.1.1.0"; // read-only (DisplayString)  
 // .iso.org.dod.internet.mgmt.mib-2.system.sysObjectID (.1.3.6.1.2.1.1.2)  
 const static char sysObjectID[] PROGMEM  = "1.3.6.1.2.1.1.2.0"; // read-only (ObjectIdentifier)  
 // .iso.org.dod.internet.mgmt.mib-2.system.sysUpTime (.1.3.6.1.2.1.1.3)  
 const static char sysUpTime[] PROGMEM   = "1.3.6.1.2.1.1.3.0"; // read-only (TimeTicks)  
 // .iso.org.dod.internet.mgmt.mib-2.system.sysContact (.1.3.6.1.2.1.1.4)  
 const static char sysContact[] PROGMEM  = "1.3.6.1.2.1.1.4.0"; // read-write (DisplayString)  
 // .iso.org.dod.internet.mgmt.mib-2.system.sysName (.1.3.6.1.2.1.1.5)  
 const static char sysName[] PROGMEM    = "1.3.6.1.2.1.1.5.0"; // read-write (DisplayString)  
 // .iso.org.dod.internet.mgmt.mib-2.system.sysLocation (.1.3.6.1.2.1.1.6)  
 const static char sysLocation[] PROGMEM  = "1.3.6.1.2.1.1.6.0"; // read-write (DisplayString)  
 // .iso.org.dod.internet.mgmt.mib-2.system.sysServices (.1.3.6.1.2.1.1.7)  
 const static char sysServices[] PROGMEM  = "1.3.6.1.2.1.1.7.0"; // read-only (Integer)  
 //  
 // Arduino defined OIDs  
 // .iso.org.dod.internet.private (.1.3.6.1.4)  
 // .iso.org.dod.internet.private.enterprises (.1.3.6.1.4.1)  
 // .iso.org.dod.internet.private.enterprises.arduino (.1.3.6.1.4.1.36582)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.0.0)  
 const static char RAWa0[] PROGMEM   = "1.3.6.1.4.1.36582.0.0"; // read-only (Integer)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.0.1)  
 const static char RAWa1[] PROGMEM   = "1.3.6.1.4.1.36582.0.1"; // read-only (Integer)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.0.2)  
 const static char RAWa2[] PROGMEM   = "1.3.6.1.4.1.36582.0.2"; // read-only (Integer)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.0.3)  
 const static char RAWa3[] PROGMEM   = "1.3.6.1.4.1.36582.0.3"; // read-only (Integer)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.0.4)  
 //const static char RAWa4[] PROGMEM   = "1.3.6.1.4.1.36582.0.4"; // read-only (Integer)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.0.5)  
 const static char RAWa5[] PROGMEM   = "1.3.6.1.4.1.36582.0.5"; // read-only (Integer)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.1.0)  
 //const static char RAWd0[] PROGMEM   = "1.3.6.1.4.1.36582.1.0"; // read-only (Integer)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.1.1)  
 //const static char RAWd1[] PROGMEM   = "1.3.6.1.4.1.36582.1.1"; // read-only (Integer)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.1.2)  
 const static char RAWd2[] PROGMEM   = "1.3.6.1.4.1.36582.1.2"; // read-only (Integer)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.1.3)  
 const static char RAWd3[] PROGMEM   = "1.3.6.1.4.1.36582.1.3"; // read-only (Integer)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.1.4)  
 const static char RAWd4[] PROGMEM   = "1.3.6.1.4.1.36582.1.4"; // read-only (Integer)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.1.5)  
 const static char RAWd5[] PROGMEM   = "1.3.6.1.4.1.36582.1.5"; // read-only (Integer)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.1.6)  
 const static char RAWd6[] PROGMEM   = "1.3.6.1.4.1.36582.1.6"; // read-only (Integer)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.1.7)  
 const static char RAWd7[] PROGMEM   = "1.3.6.1.4.1.36582.1.7"; // read-only (Integer)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.1.8)  
 const static char RAWd8[] PROGMEM   = "1.3.6.1.4.1.36582.1.8"; // read-only (Integer)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.1.9)  
 const static char RAWd9[] PROGMEM   = "1.3.6.1.4.1.36582.1.9"; // read-only (Integer)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.1.10)  
 //const static char RAWd10[] PROGMEM   = "1.3.6.1.4.1.36582.1.10"; // read-only (Integer)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.1.11)  
 //const static char RAWd11[] PROGMEM   = "1.3.6.1.4.1.36582.1.11"; // read-only (Integer)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.1.12)  
 //const static char RAWd12[] PROGMEM   = "1.3.6.1.4.1.36582.1.12"; // read-only (Integer)  
 // .iso.org.dod.internet.private.enterprises.arduino.RAW (.1.3.6.1.4.1.36582.1.13)  
 //const static char RAWd13[] PROGMEM   = "1.3.6.1.4.1.36582.1.13"; // read-only (Integer)  
 //  
 //  
 // RFC1213 local values  
 static char locDescr[]       = "Agentuino, a light-weight SNMP Agent."; // read-only (static)  
 static char locObjectID[]      = "1.3.6.1.3.2009.0";            // read-only (static)  
 static uint32_t locUpTime      = 0;                    // read-only (static)  
 static char locContact[20]     = "";              // should be stored/read from EEPROM - read/write (not done for simplicity)  
 static char locName[20]       = "Sensor";    // should be stored/read from EEPROM - read/write (not done for simplicity)  
 static char locLocation[20]     = "";            // should be stored/read from EEPROM - read/write (not done for simplicity)  
 static int32_t locServices     = 7;                    // read-only (static)  
 uint32_t prevMillis = millis();  
 char oid[SNMP_MAX_OID_LEN];  
 SNMP_API_STAT_CODES api_status;  
 SNMP_ERR_CODES status;  
 void pduReceived()  
 {  
  SNMP_PDU pdu;  
  //  
  #ifdef DEBUG  
   Serial << F("UDP Packet Received Start..") << F(" RAM:") << freeMemory() << endl;  
  #endif  
  //  
  api_status = Agentuino.requestPdu(&pdu);  
  //  
  if ( pdu.type == SNMP_PDU_GET || pdu.type == SNMP_PDU_GET_NEXT || pdu.type == SNMP_PDU_SET  
   && pdu.error == SNMP_ERR_NO_ERROR && api_status == SNMP_API_STAT_SUCCESS ) {  
   //  
   pdu.OID.toString(oid);  
   //  
   //Serial << "OID: " << oid << endl;  
   //  
   if ( strcmp_P(oid, sysDescr ) == 0 ) {  
    // handle sysDescr (set/get) requests  
    if ( pdu.type == SNMP_PDU_SET ) {  
     // response packet from set-request - object is read-only  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = SNMP_ERR_READ_ONLY;  
    } else {  
     // response packet from get-request - locDescr  
     status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, locDescr);  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    }  
    //  
    #ifdef DEBUG  
     Serial << F("sysDescr...") << locDescr << F(" ") << pdu.VALUE.size << endl;  
    #endif  
   } else if ( strcmp_P(oid, sysUpTime ) == 0 ) {  
    // handle sysName (set/get) requests  
    if ( pdu.type == SNMP_PDU_SET ) {  
     // response packet from set-request - object is read-only  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = SNMP_ERR_READ_ONLY;  
    } else {  
     // response packet from get-request - locUpTime  
     status = pdu.VALUE.encode(SNMP_SYNTAX_TIME_TICKS, locUpTime);  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    }  
    //  
    #ifdef DEBUG  
     Serial << F("sysUpTime...") << locUpTime << F(" ") << pdu.VALUE.size << endl;  
    #endif  
   } else if ( strcmp_P(oid, sysName ) == 0 ) {  
    // handle sysName (set/get) requests  
    if ( pdu.type == SNMP_PDU_SET ) {  
     // response packet from set-request - object is read/write  
     status = pdu.VALUE.decode(locName, strlen(locName));  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    } else {  
     // response packet from get-request - locName  
     status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, locName);  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    }  
    //  
    #ifdef DEBUG  
     Serial << F("sysName...") << locName << F(" ") << pdu.VALUE.size << endl;  
    #endif  
   } else if ( strcmp_P(oid, sysContact ) == 0 ) {  
    // handle sysContact (set/get) requests  
    if ( pdu.type == SNMP_PDU_SET ) {  
     // response packet from set-request - object is read/write  
     status = pdu.VALUE.decode(locContact, strlen(locContact));  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    } else {  
     // response packet from get-request - locContact  
     status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, locContact);  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    }  
    //  
    #ifdef DEBUG  
     Serial << F("sysContact...") << locContact << F(" ") << pdu.VALUE.size << endl;  
    #endif  
   } else if ( strcmp_P(oid, sysLocation ) == 0 ) {  
    // handle sysLocation (set/get) requests  
    if ( pdu.type == SNMP_PDU_SET ) {  
     // response packet from set-request - object is read/write  
     status = pdu.VALUE.decode(locLocation, strlen(locLocation));  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    } else {  
     // response packet from get-request - locLocation  
     status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, locLocation);  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    }  
    //  
    #ifdef DEBUG  
     Serial << F("sysLocation...") << locLocation << F(" ") << pdu.VALUE.size << endl;  
    #endif  
   } else if ( strcmp_P(oid, sysServices) == 0 ) {  
    // handle sysServices (set/get) requests  
    if ( pdu.type == SNMP_PDU_SET ) {  
     // response packet from set-request - object is read-only  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = SNMP_ERR_READ_ONLY;  
    } else {  
     // response packet from get-request - locServices  
     status = pdu.VALUE.encode(SNMP_SYNTAX_INT, locServices);  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    }  
    //  
    #ifdef DEBUG  
     Serial << F("locServices...") << locServices << F(" ") << pdu.VALUE.size << endl;  
    #endif  
      } else if ( strcmp_P(oid, RAWa0) == 0 ) {  
    int rawReading = 0;  
    // handle RAW (set/get) requests  
    if ( pdu.type == SNMP_PDU_SET ) {  
     // response packet from set-request - object is read-only  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = SNMP_ERR_READ_ONLY;  
    } else {  
     // response packet from get-request - RAW  
            rawReading = analogRead(A0);  // Range : 0..1024  
            status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
            pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    }  
    //  
    #ifdef DEBUG  
     Serial << F("RAWa0...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
    #endif  
    } else if ( strcmp_P(oid, RAWa1) == 0 ) {  
    int rawReading = 0;  
    // handle RAW (set/get) requests  
    if ( pdu.type == SNMP_PDU_SET ) {  
     // response packet from set-request - object is read-only  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = SNMP_ERR_READ_ONLY;  
    } else {  
     // response packet from get-request - RAW  
            rawReading = analogRead(A1);  // Range : 0..1024  
            status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
            pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    }  
    //  
    #ifdef DEBUG  
     Serial << F("RAW...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
    #endif  
    } else if ( strcmp_P(oid, RAWa2) == 0 ) {  
    int rawReading = 0;  
    // handle RAW (set/get) requests  
    if ( pdu.type == SNMP_PDU_SET ) {  
     // response packet from set-request - object is read-only  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = SNMP_ERR_READ_ONLY;  
    } else {  
     // response packet from get-request - RAW  
            rawReading = analogRead(A2);  // Range : 0..1024  
            status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
            pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    }  
    //  
    #ifdef DEBUG  
     Serial << F("RAWa2...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
    #endif  
      } else if ( strcmp_P(oid, RAWa3) == 0 ) {  
    int rawReading = 0;  
    // handle RAW (set/get) requests  
    if ( pdu.type == SNMP_PDU_SET ) {  
     // response packet from set-request - object is read-only  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = SNMP_ERR_READ_ONLY;  
    } else {  
     // response packet from get-request - RAW  
            rawReading = analogRead(A3);  // Range : 0..1024  
            status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
            pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    }  
    //  
    #ifdef DEBUG  
     Serial << F("RAWa3...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
    #endif  
 //     } else if ( strcmp_P(oid, RAWa4) == 0 ) {  
 //   int rawReading = 0;  
 //   // handle RAW (set/get) requests  
 //   if ( pdu.type == SNMP_PDU_SET ) {  
 //    // response packet from set-request - object is read-only  
 //    pdu.type = SNMP_PDU_RESPONSE;  
 //    pdu.error = SNMP_ERR_READ_ONLY;  
 //   } else {  
 //    // response packet from get-request - RAW  
 //           rawReading = analogRead(A4);  // Range : 0..1024  
 //           status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
 //           pdu.type = SNMP_PDU_RESPONSE;  
 //    pdu.error = status;  
 //   }  
 //   //  
 //   #ifdef DEBUG  
 //    Serial << F("RAWa4...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
 //   #endif  
      } else if ( strcmp_P(oid, RAWa5) == 0 ) {  
    int rawReading = 0;  
    // handle RAW (set/get) requests  
    if ( pdu.type == SNMP_PDU_SET ) {  
     // response packet from set-request - object is read-only  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = SNMP_ERR_READ_ONLY;  
    } else {  
     // response packet from get-request - RAW  
            rawReading = analogRead(A5);  // Range : 0..1024  
            status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
            pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    }  
    //  
    #ifdef DEBUG  
     Serial << F("RAWa5...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
    #endif  
 //      } else if ( strcmp_P(oid, RAWd0) == 0 ) {  
 //   int rawReading = 0;  
 //   // handle RAW (set/get) requests  
 //   if ( pdu.type == SNMP_PDU_SET ) {  
 //    // response packet from set-request - object is read-only  
 //    pdu.type = SNMP_PDU_RESPONSE;  
 //    pdu.error = SNMP_ERR_READ_ONLY;  
 //   } else {  
 //    // response packet from get-request - RAW  
 //           if (digitalRead(0) == LOW) {  
 //            rawReading = 1; // Range : 0..1  
 //           }  
 //           status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
 //           pdu.type = SNMP_PDU_RESPONSE;  
 //    pdu.error = status;  
 //   }  
 //   //  
 //   #ifdef DEBUG  
 //    Serial << F("RAWd0...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
 //   #endif  
 //     } else if ( strcmp_P(oid, RAWd1) == 0 ) {  
 //   int rawReading = 0;  
 //   // handle RAW (set/get) requests  
 //   if ( pdu.type == SNMP_PDU_SET ) {  
 //    // response packet from set-request - object is read-only  
 //    pdu.type = SNMP_PDU_RESPONSE;  
 //    pdu.error = SNMP_ERR_READ_ONLY;  
 //   } else {  
 //    // response packet from get-request - RAW  
 //           if (digitalRead(1) == LOW) {  
 //            rawReading = 1; // Range : 0..1  
 //           }           status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
 //           pdu.type = SNMP_PDU_RESPONSE;  
 //    pdu.error = status;  
 //   }  
 //   //  
 //   #ifdef DEBUG  
 //    Serial << F("RAWd1...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
 //   #endif  
      } else if ( strcmp_P(oid, RAWd2) == 0 ) {  
    int rawReading = 0;  
    // handle RAW (set/get) requests  
    if ( pdu.type == SNMP_PDU_SET ) {  
     // response packet from set-request - object is read-only  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = SNMP_ERR_READ_ONLY;  
    } else {  
     // response packet from get-request - RAW  
            if (digitalRead(2) == LOW) {  
             rawReading = 1; // Range : 0..1  
            }  
            status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
            pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    }  
    //  
    #ifdef DEBUG  
     Serial << F("RAWd2...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
    #endif  
      } else if ( strcmp_P(oid, RAWd3) == 0 ) {  
    int rawReading = 0;  
    // handle RAW (set/get) requests  
    if ( pdu.type == SNMP_PDU_SET ) {  
     // response packet from set-request - object is read-only  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = SNMP_ERR_READ_ONLY;  
    } else {  
     // response packet from get-request - RAW  
            if (digitalRead(3) == LOW) {  
             rawReading = 1; // Range : 0..1  
            }  
            status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
            pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    }  
    //  
    #ifdef DEBUG  
     Serial << F("RAWd3...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
    #endif  
      } else if ( strcmp_P(oid, RAWd4) == 0 ) {  
    int rawReading = 0;  
    // handle RAW (set/get) requests  
    if ( pdu.type == SNMP_PDU_SET ) {  
     // response packet from set-request - object is read-only  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = SNMP_ERR_READ_ONLY;  
    } else {  
     // response packet from get-request - RAW  
            if (digitalRead(4) == LOW) {  
             rawReading = 1; // Range : 0..1  
            }  
            status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
            pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    }  
    //  
    #ifdef DEBUG  
     Serial << F("RAWd4...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
    #endif  
      } else if ( strcmp_P(oid, RAWd5) == 0 ) {  
    int rawReading = 0;  
    // handle RAW (set/get) requests  
    if ( pdu.type == SNMP_PDU_SET ) {  
     // response packet from set-request - object is read-only  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = SNMP_ERR_READ_ONLY;  
    } else {  
     // response packet from get-request - RAW  
            if (digitalRead(5) == LOW) {  
             rawReading = 1; // Range : 0..1  
            }  
            status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
            pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    }  
    //  
    #ifdef DEBUG  
     Serial << F("RAWd5...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
    #endif  
      } else if ( strcmp_P(oid, RAWd6) == 0 ) {  
    int rawReading = 0;  
    // handle RAW (set/get) requests  
    if ( pdu.type == SNMP_PDU_SET ) {  
     // response packet from set-request - object is read-only  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = SNMP_ERR_READ_ONLY;  
    } else {  
     // response packet from get-request - RAW  
            if (digitalRead(6) == LOW) {  
             rawReading = 1; // Range : 0..1  
            }  
            status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
            pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    }  
    //  
    #ifdef DEBUG  
     Serial << F("RAWd6...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
    #endif  
      } else if ( strcmp_P(oid, RAWd7) == 0 ) {  
    int rawReading = 0;  
    // handle RAW (set/get) requests  
    if ( pdu.type == SNMP_PDU_SET ) {  
     // response packet from set-request - object is read-only  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = SNMP_ERR_READ_ONLY;  
    } else {  
     // response packet from get-request - RAW  
            if (digitalRead(7) == LOW) {  
             rawReading = 1; // Range : 0..1  
            }  
            status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
            pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    }  
    //  
    #ifdef DEBUG  
     Serial << F("RAWd7...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
    #endif  
      } else if ( strcmp_P(oid, RAWd8) == 0 ) {  
    int rawReading = 0;  
    // handle RAW (set/get) requests  
    if ( pdu.type == SNMP_PDU_SET ) {  
     // response packet from set-request - object is read-only  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = SNMP_ERR_READ_ONLY;  
    } else {  
     // response packet from get-request - RAW  
            if (digitalRead(8) == LOW) {  
             rawReading = 1; // Range : 0..1  
            }  
            status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
            pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    }  
    //  
    #ifdef DEBUG  
     Serial << F("RAWd8...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
    #endif  
      } else if ( strcmp_P(oid, RAWd9) == 0 ) {  
    int rawReading = 0;  
    // handle RAW (set/get) requests  
    if ( pdu.type == SNMP_PDU_SET ) {  
     // response packet from set-request - object is read-only  
     pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = SNMP_ERR_READ_ONLY;  
    } else {  
     // response packet from get-request - RAW  
            if (digitalRead(9) == LOW) {  
             rawReading = 1; // Range : 0..1  
            }  
            status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
            pdu.type = SNMP_PDU_RESPONSE;  
     pdu.error = status;  
    }  
    //  
    #ifdef DEBUG  
     Serial << F("RAWd9...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
    #endif  
 //     } else if ( strcmp_P(oid, RAWd10) == 0 ) {  
 //   int rawReading = 0;  
 //   // handle RAW (set/get) requests  
 //   if ( pdu.type == SNMP_PDU_SET ) {  
 //    // response packet from set-request - object is read-only  
 //    pdu.type = SNMP_PDU_RESPONSE;  
 //    pdu.error = SNMP_ERR_READ_ONLY;  
 //   } else {  
 //    // response packet from get-request - RAW  
 //           if (digitalRead(10) == LOW) {  
 //            rawReading = 1; // Range : 0..1  
 //           }  
 //           status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
 //           pdu.type = SNMP_PDU_RESPONSE;  
 //    pdu.error = status;  
 //   }  
 //   //  
 //   #ifdef DEBUG  
 //    Serial << F("RAWd10...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
 //   #endif  
 //     } else if ( strcmp_P(oid, RAWd11) == 0 ) {  
 //   int rawReading = 0;  
 //   // handle RAW (set/get) requests  
 //   if ( pdu.type == SNMP_PDU_SET ) {  
 //    // response packet from set-request - object is read-only  
 //    pdu.type = SNMP_PDU_RESPONSE;  
 //    pdu.error = SNMP_ERR_READ_ONLY;  
 //   } else {  
 //    // response packet from get-request - RAW  
 //           if (digitalRead(11) == LOW) {  
 //            rawReading = 1; // Range : 0..1  
 //           }  
 //           status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
 //           pdu.type = SNMP_PDU_RESPONSE;  
 //    pdu.error = status;  
 //   }  
 //   //  
 //   #ifdef DEBUG  
 //    Serial << F("RAWd11...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
 //   #endif  
 //     } else if ( strcmp_P(oid, RAWd12) == 0 ) {  
 //   int rawReading = 0;  
 //   // handle RAW (set/get) requests  
 //   if ( pdu.type == SNMP_PDU_SET ) {  
 //    // response packet from set-request - object is read-only  
 //    pdu.type = SNMP_PDU_RESPONSE;  
 //    pdu.error = SNMP_ERR_READ_ONLY;  
 //   } else {  
 //    // response packet from get-request - RAW  
 //           if (digitalRead(12) == LOW) {  
 //            rawReading = 1; // Range : 0..1  
 //           }  
 //           status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
 //           pdu.type = SNMP_PDU_RESPONSE;  
 //    pdu.error = status;  
 //   }  
 //   //  
 //   #ifdef DEBUG  
 //    Serial << F("RAWd12...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
 //   #endif  
 //     } else if ( strcmp_P(oid, RAWd13) == 0 ) {  
 //   int rawReading = 0;  
 //   // handle RAW (set/get) requests  
 //   if ( pdu.type == SNMP_PDU_SET ) {  
 //    // response packet from set-request - object is read-only  
 //    pdu.type = SNMP_PDU_RESPONSE;  
 //    pdu.error = SNMP_ERR_READ_ONLY;  
 //   } else {  
 //    // response packet from get-request - RAW  
 //           if (digitalRead(13) == LOW) {  
 //            rawReading = 1; // Range : 0..1  
 //           }  
 //           status = pdu.VALUE.encode(SNMP_SYNTAX_INT, rawReading);  
 //           pdu.type = SNMP_PDU_RESPONSE;  
 //    pdu.error = status;  
 //   }  
 //   //  
 //   #ifdef DEBUG  
 //    Serial << F("RAWd13...") << rawReading << F(" ") << pdu.VALUE.size << endl;  
 //   #endif  
   } else {  
    // oid does not exist  
    //  
    // response packet - object not found  
    pdu.type = SNMP_PDU_RESPONSE;  
    pdu.error = SNMP_ERR_NO_SUCH_NAME;  
   }  
   //  
   Agentuino.responsePdu(&pdu);  
  }  
  //  
  Agentuino.freePdu(&pdu);  
  //  
  //Serial << "UDP Packet Received End.." << " RAM:" << freeMemory() << endl;  
 }  
 void setup()  
 {  
  Serial.begin(9600);  
  Ethernet.begin(mac, ip);  
  //  
  //  
  pinMode(A1, INPUT);  
  pinMode(A0, INPUT);  
  pinMode(A2, INPUT);  
  pinMode(A3, INPUT);  
 // pinMode(A4, INPUT);  
  pinMode(A5, INPUT);  
 // pinMode(0, INPUT);  
 // pinMode(1, INPUT);  
  pinMode(2, INPUT);  
  pinMode(3, INPUT);  
  pinMode(4, INPUT);  
  pinMode(5, INPUT);  
  pinMode(6, INPUT);  
  pinMode(7, INPUT);  
  pinMode(8, INPUT);  
  pinMode(9, INPUT);  
 // pinMode(10, INPUT);  
 // pinMode(11, INPUT);  
 // pinMode(12, INPUT);  
 // pinMode(13, INPUT);  
 //digitalWrite(0, HIGH);  
 //digitalWrite(1, HIGH);  
 digitalWrite(2, HIGH);  
 digitalWrite(3, HIGH);  
 digitalWrite(4, HIGH);  
 digitalWrite(5, HIGH);  
 digitalWrite(6, HIGH);  
 digitalWrite(7, HIGH);  
 digitalWrite(8, HIGH);  
 digitalWrite(9, HIGH);  
 //digitalWrite(10, HIGH);  
 //digitalWrite(11, HIGH);  
 //digitalWrite(12, HIGH);  
 //digitalWrite(13, HIGH);  
  api_status = Agentuino.begin();  
  if ( api_status == SNMP_API_STAT_SUCCESS ) {  
   //  
   Agentuino.onPduReceive(pduReceived);  
   //  
   delay(10);  
   //  
   Serial << F("SNMP Agent Initalized...") << endl;  
   //  
   return;  
  }  
  //  
  delay(10);  
  //  
  Serial << F("SNMP Agent Initalization Problem...") << status << endl;  
 }  
 void loop()  
 {  
  // listen/handle for incoming SNMP requests  
  Agentuino.listen();  
  //  
  // sysUpTime - The time (in hundredths of a second) since  
  // the network management portion of the system was last  
  // re-initialized.  
  if ( millis() - prevMillis > 1000 ) {  
   // increment previous milliseconds  
   prevMillis += 1000;  
   //  
   // increment up-time counter  
   locUpTime += 100;  
  }  
 }  
Formatted for web with http://codeformatter.blogspot.com/ 

With this sketch, the pin inputs I can read are:
Analog 0, 1, 2, 3, 5 - Values returned - integer 0 - 1024 (0 - 5V Input)
Digital 2, 3, 4, 5, 6, 7, 8, 9 - Values returned - integer 0 (Open to GND) or 1 (Closed to GND)

I'm only using analog pin 1 to read in my 15 PSI sensor. At first, I was doing all the math on the Arduino, but it made it less flexible, so I've moved all my math and formulas for water - Analog input to Volts, PSI to inch/feet of water, and height of water to gallons based on tank shape and size - into PowerShell.


To read the sensor, you apply 5V power and ground from the Arduino to it and measure the signal input as 0 - 1024 input.

I noticed the math wasn't working out when I checked the actual inches of water versus what my sensor was reporting. The biggest variable appears to be the input voltage from your power source. I have found some USB wall adapters vary as much as 4.55 - 4.98 VDC. So, be sure to take the actual reference voltage before finalizing your formulas on reading in from analog pins.


I'll share my PowerShell function's details - which makes this easy to adjust reference voltage input - in Part 4.


Tuesday, April 14, 2015

Use Powershell to Change Host's FQDN / Suffix

I'm attempting to setup 6 Windows Server 2012 R2 RDP Session Host servers all via the CLI using PowerShell.

When it came time to configure the machine's name, domain, and Primary FQDN, I could rename the computer (Rename-Computer), add it to a domain (Add-Computer), but I got stuck trying to update / change the Primary DNS suffix of this computer, as there was no way I could find - other than using ancient netdom.exe, not my idea of PowerShell.

I hacked around with this for a bit on some Windows Server 2012 R2 servers modifying the registry to make it happen. Updating Domain and NV Domain in the [HKLM\System\CurrentControlSet\Services\Tcpip\Parameters] initially seemed to work with no issue - https://technet.microsoft.com/en-us/library/aa998420%28v=exchg.80%29.aspx.

Later, I discovered that the SPN record information for AD computers also needed to be updated or you get "The security database on the server does not have a computer account for this workstation trust relationship" error.

Below is what I have been using and is seems to work, as long as it is Run as Administrator as a Local Administrator and from a Domain Admin account (if connected to an AD domain.)

Current version at https://github.com/cajeeper/PowerShell/blob/master/Update-HostFQDN.ps1

Latest change: Added updating the dnsHostName attribute - Thanks to Nan Zhang

$computerName = $env:computername

$DNSSuffix = "abc.com"

$oldDNSSuffix = (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\" -Name "NV Domain")."NV Domain"

#Update primary DNS Suffix for FQDN
Set-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\" -Name Domain -Value $DNSSuffix
Set-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\" -Name "NV Domain" -Value $DNSSuffix

#Update DNS Suffix Search List - Win8/2012 and above - if needed
#Set-DnsClientGlobalSetting -SuffixSearchList $oldDNSSuffix,$DNSSuffix

#Update AD's SPN records for machine if part of an AD domain
if ((gwmi win32_computersystem).partofdomain -eq $true) {
     $searchAD = new-object System.DirectoryServices.DirectorySearcher
     $searchAD.filter = "(&(objectCategory=computer)(cn=$($computerName)))"
     $searchADItem = $searchAD.FindAll() | select -first 1
     $adObj= [ADSI] $searchADItem.Path
     $oldadObjSPN = $searchADItem.Properties.serviceprincipalname
     $adObj.Put('serviceprincipalname',($oldadObjSPN -replace $oldDNSSuffix, $DNSSuffix))
     $oldadObjDNS = $searchADItem.Properties.dnsHostName
     $adObj.Put('dnsHostName',($oldadObjDNS -replace $oldDNSSuffix, $DNSSuffix))
     $adObj.setinfo()
     #$adObj.Get('serviceprincipalname')
     #$adObj.Get('dnsHostName')
}
Formatted for web with http://codeformatter.blogspot.com/ 


Before changing DNS Suffix via CLI / PowerShell

Ran CLI / PowerShell script to update Primary DNS Suffix

After Running CLI / PowerShell script

Upward and onward!

Tuesday, March 10, 2015

Home Water System Pt 2 - Get and Set SNMP

With PowerShell, there's a couple of great options to purchase SNMP libraries to perform SNMP get and set. I had some success with OlePrn.OleSNMP class as UsPeoples, over at tomorrow.uspeoples.org, wrote about, but my success was short lived. It only supports SNMPv1 and attempting to do any of the Methods from three of my Windows 7 computers returns:

Exception calling "Get" with "1" argument(s): "WinSNMP API Error: For internal/undefined errors"
At line:1 char:10
+ $SNMP.Get <<<< (".1.3.6.1.2.1.1.5.0")
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ComMethodTargetInvocation

Alternatively, I've been using a command-line (CLI) tool with PowerShell and capturing the output. Since there are a few old CLI tools for SNMP that are free, like snmpget.exe and snmpset.exe by snmpsoft (dot) com. I've been using this for my SNMP get and set routines to control my home brewed SCADA system for nearly a year now with no issue.

So, where do we get started?

You can capture any output from a CLI tool fairly easily. Just run the program inside of a PowerShell variable value, as such:

$output = mycli.exe

That seems simple enough. Next, you need to ensure that you capture all of your CLI tool's output - or at least I prefer to - including any errors which sometimes sneak by. about_Redirection describes that 2>&1 at the end of your CLI tool will capture your success output along with any error output. You can also include all output with *>&1 - to each their own, but I prefer just errors and success output.

$output = mycli.exe 2>&1

Wrap it all tight in a few functions and you just saved some dollars... on SNMP get and set funcitons.

PowerShell get-snmp function
  <#  
 .SYNOPSIS  
  Function to capture output from snmpget exe CLI tool.  
    
 .DESCRIPTION   
  Based on the parameters sent, the CLI tool will be triggered and the output will be returned based on the output.  
    
 .NOTES   
  Author   : Justin Bennett   
  Date     : 2015-03-10  
  Contact  : http://www.allthingstechie.net
  Revision : v1.01
  Changes  : v1.01 - Changed Write-Error to Throw  
   
 .PARAMETER ip  
  IP Address or Hostname  
   
 .PARAMETER ver  
  SNMP Version  
   
 .PARAMETER community  
  SNMP Community String to read the OID  
   
 .PARAMETER OID  
  SNMP OID to read  
   
 .PARAMETER returntype  
  Specify the preferred return type - gauge32, integer, string (Default), timeticks, or todate   
    
 .EXAMPLE  
  C:\PS> get-snmp -ip "192.168.0.10" -ver "1" -community "public" -oid "1.3.6.1.2.1.1.5.0"  
  sysLocation-Example  
    
 .EXAMPLE  
  C:\PS> #Get SNMP Contact Info  
  C:\PS> $oid = "1.3.6.1.2.1.1.4.0", "1.3.6.1.2.1.1.5.0", "1.3.6.1.2.1.1.6.0"  
  C:\PS> $oid | % { get-snmp -ip "192.168.0.10" -ver "1" -community "public" -oid $_ }  
  sysName-Example  
  sysLocation-Example  
  sysServices-Example  
 #>  
 function get-snmp {  
      [CmdletBinding()]  
      param (  
           [parameter(Mandatory = $true)] [string]$ip,  
           [parameter(Mandatory = $true)] [string]$ver,  
           [parameter(Mandatory = $true)] [string]$community,  
           [parameter(Mandatory = $true)] [string]$oid,  
           [parameter(Mandatory = $false)] [ValidateSet("timeticks","integer","gauge32","string","todate")] [string]$returntype = "string"  
      )  
        
      #location of the snmpget program  
      $snmpdir = ".\"  
   
      $output = (. $snmpdir\snmpget.exe -v:$ver -c:"$community" -r:$ip -o:$oid 2>&1) | select -skip 3  
      $outputsplit = $output -split "="  
        
      if ($outputsplit[0].tolower() -ne "oid") {  
           #one more try  
           $output = (. $snmpdir\snmpget.exe -v:$ver -c:"$community" -r:$ip -o:$oid 2>&1) | select -skip 3  
           $outputsplit = $output -split "="  
      }  
        
      switch ($outputsplit[0].tolower()) {  
       "oid" {  
           if($returntype -eq $null) { $returntype = $outputsplit[3] }  
           switch ($returntype.tolower()) {  
                 "timeticks" {  
                     $time = ($outputsplit[5]) -replace "\.", ":" -split ":"  
                     return New-TimeSpan -hour $time[0] -min $time[1]  
                     }  
                 "integer" {  
                     return [int]$outputsplit[5]  
                     }  
                 "gauge32" {  
                     return [int]$outputsplit[5]  
                     }  
                 "string" {  
                     return [string]$outputsplit[5]  
                     }  
                 "todate" {  
                     return [datetime](get-date (($outputsplit[5]) -replace "`"",""))  
                     }  
                 default{  
                     return $outputsplit[5]  
                }  
           }  
    }  
       default {  
             throw $output  
           }  
      }  
 }    
Formatted for web with http://codeformatter.blogspot.com/ 

Example Output

Cheers. Even more pieces to the puzzle coming soon.


Monday, March 9, 2015

Home Water System Pt 1 - Give yourself to the Dark Side... of automation.


In the last few years, I've been leveraging PowerShell more and more. So much more, that I'm using it at home to control my fresh water well.

Before I jump head first into the land of crazy, I'm going preface, this will be split up over a few posts. My goal by sharing this isn't to see a future full of PowerShell'd home water systems, but rather just to spark interest in others to break technical norms and explore what you can build. I can see many other uses for simple controlled SNMP devices all over.

First, I'll give you a quick overview of how this is working logically as a system, then I'll show each key piece to the PowerShell puzzle in subsequent posts.

To understand why I've done this and to see the benefit, I have to explain the traditional configuration of a fresh water well partnered with a ground level storage tank.

WARNING: I'm a hack at best when it comes to water - so please don't take what I've done or what I say about water systems as advise. Consult a local professional.

As described by Clean Water Store, my home water well "system uses a large storage tank to store the water before it is pumped again to the house", as depicted below. The portion I choose PowerShell to manage is controlling the start/stop and reading the amp draw for the submersible deep well pump and reading the storage tank's water level with a pressure sensor.

http://support.cleanwaterstore.com/blog/how-home-well-water-pump-and-pressure-systems-work/

With those two components, I have two .ps1 scripts that run. The first .ps1 is used to fill my storage tank every other day in set 15 min increments for the course of one evening. This is done by 1) reading the water level in the tank to limit the max amount of water 2) the amperage being drawn off the pump when it is running as to not damage the pump should it draw too much amps or begin to suck air. At the end of each fill, a follow-up report is sent with the details of each run. The second .ps1 reports on the tank's water level readings sent every 12 hours. The booster pump still depends on a pressure switch to run and is not yet monitored.

Two main benefits in doing this is: 1) I know how much water is in my water storage tank, and 2) I know as soon as my deep well pump begins to have a problem, so I have time to react before all of my stored water is gone. Additionally, I have the ability to control the timing of how long and when the pump runs beyond the limits of mechanical control timers.

As you can see, PowerShell can be leveraged as your own SCADA system limited by your own imagination.

My system consists of a low power consumption PC running Windows 7 (Dell OptiPlex FX170), an APC Rack PDU AP7940 to control the well pump, and an Arduino UNO R3 with a 3 wire 15 psi water pressure sensor and a Ethernet Shield. The script makes all the calls via SNMP to read the devices and control the power to the well pump.

Pre-Automation

Building the "water shed" for my pumps
Assembling Pumps and Pipes
Deep pump controller wired to tank's float shut-off and bypass switch 
Pipes and pumps connected


Post-Automation

APC Rack PDU Interface
Run Well Pump .ps1 Report Image 
APC Rack PDU
Deep pump wired to C19 plug and bypass switch

Arduino with Ethernet shield used to read water pressure

Pressure sensor
Thoroughly wrapped by black 10 mil pipe wrap

More explanation of this mayhem to be continued...

Saturday, February 7, 2015

Backup Zen Load Balancer using PowerShell and System.Net.WebClient

I really like backups. Full backups of systems are nice. Call me crazy, but they are to me. When we started using ZenLoadBalancer, we ran into a small dilemma.

We use Microsoft Data Protection Manager 2012 for our primary backup system and Hyper-V 2008 R2 / 2012 for our primary hyper-visors - not the best of either and the equivalent of oil and water when it comes to backing up virtual Linux machines. It will pause the virtual machine during backup and cause the load balancers - if in a zen cluster like ours - to fail-over.

So... used good old PowerShell to write a backup routine.

What does it do?
  1. Logs into the management console of the Zen Load Balancer
  2. Creates a backup in the console
  3. Downloads the backup file to a local directory
  4. Optionally - Multiple ZenLB backup jobs at once, creates a log file, sends email notifications of statuses, and purges old backup files
We have four ZenLoadBalancer we're using and this script has been working since September of last year with no issues.

Note: Its scheduled to run three times a day via Task Scheduler using:
  • powershell.exe -executionpolicy bypass -file backup-zenlb.ps1
 <#  
 .SYNOPSIS  
  Create ZenLoadBalancer Backup and Download  
 .DESCRIPTION  
  Based on the parameters saved in the .ps1, each Zen Load Balancer will be contacted to trigger a backup and download and save the backup file(s) by host(s) and date/time.  
  Optionally: Log, Email Notify, Purge Old Backups  
 .NOTES  
  Author   : Justin Bennett  
  Date    : 2014-09-26  
  Contact  : http://www.allthingstechie.net/  
  Revision  : v1.1  
  History  : v1.0 written for Zen 3.03  
        v1.1 added support for Zen 3.05  
  References : Allow untrusted SSL - http://blogs.technet.com/b/bshukla/archive/2010/04/12/ignoring-ssl-trust-in-powershell-system-net-webClient.aspx  
 #>  
 #uservariables  
 #  
 $scriptRoot = $pwd  
 #$scriptRoot = "D:\ZenLoadBalancers\batch\"  
 $backupJobs = @{}  
 $backupJobs[0] = @{}  
 $backupJobs[0]["backupName"]  = "zenlb1"  
 $backupJobs[0]["backupRoot"] = "$($pwd)\"  
 #$backupJobs[0]["backupRoot"] = "D:\ZenLoadBalancers\backups\"  
 $backupJobs[0]["hostIP"]    = "192.168.0.11:444"  
 $backupJobs[0]["username"]   = "admin"  
 $backupJobs[0]["password"]   = "pass"  
 $backupJobs[0]["domain"]    = ""  
 #$backupJobs[1] = @{}  
 #$backupJobs[1]["backupName"]  = "zenlb2"  
 #$backupJobs[1]["backupRoot"] = "D:\ZenLoadBalancers\backups\"  
 #$backupJobs[1]["hostIP"]    = "192.168.0.12:444"  
 #$backupJobs[1]["username"]   = "admin"  
 #$backupJobs[1]["password"]   = "pass"  
 #$backupJobs[1]["domain"]    = ""  
 #$backupJobs[2] = @{}  
 #$backupJobs[2]["backupName"]  = "zenlb3"  
 #$backupJobs[2]["backupRoot"] = "D:\ZenLoadBalancers\backups\"  
 #$backupJobs[2]["hostIP"]    = "192.168.0.13:444"  
 #$backupJobs[2]["username"]   = "admin"  
 #$backupJobs[2]["password"]   = "pass"  
 #$backupJobs[2]["domain"]    = ""  
 #$backupJobs[3] = @{}  
 #$backupJobs[3]["backupName"]  = "zenlb4"  
 #$backupJobs[3]["backupRoot"] = "D:\ZenLoadBalancers\backups\"  
 #$backupJobs[3]["hostIP"]    = "192.168.0.14:444"  
 #$backupJobs[3]["username"]   = "admin"  
 #$backupJobs[3]["password"]   = "pass"  
 #$backupJobs[3]["domain"]    = ""  
 $purgeOldBackups = $false  
 #$purgeOldBackups = $true  
 $purgeDaysToKeep = 1  
 $purgeRoot = "."  
 #$purgeRoot = "D:\ZenLoadBalancers\backups\"  
 $createEmail = $false  
 #$createEmail = $true  
 $subjectTitle = "Backup Zen Load Balancers - %status%"  
 $emailFrom     = "task-computer@local.domain"  
 #$emailTo     = "admin1@email.com", "admin2@email.com"  
 $emailTo     = "admin1@email.com"  
 $smtpServer = "smtp.local.domain"  
 $createlog = $true  
 $logRoot  = $pwd  
 #$logRoot  = "D:\ZenLoadBalancers\log\"  
 #$debug   = $true  
 $debug   = $false  
 #runtime variables  
 #  
 $scriptName = $MyInvocation.MyCommand.Name  
 $scriptpath = $Myinvocation.Mycommand.Path  
 $start = get-date  
 $log = New-Object -TypeName "System.Text.StringBuilder" "";  
 [void]$log.appendline((("Starting Script - ")+($start)))  
 if ($debug) { write-host "Starting Script - $($start)" }  
 $status = @{}  
 function writeLog {  
      $exist = Test-Path "$($logRoot)\$($scriptName).log"  
      $logFile = New-Object System.IO.StreamWriter("$($logRoot)\$($scriptName).log", $exist)  
      $logFile.write($log)  
      $logFile.close()  
 }  
 function sendEmail {  
      try {  
      [void]$log.appendline((("Emailing Results - ")+(get-date)))  
      if ($debug) { write-host "Emailing Results" }  
      $body = "<style type=""text/css"">  
      span { font-family: Calibri, verdana,arial,sans-serif; }  
      table {  
           font-family: Calibri, verdana,arial,sans-serif;  
           color:#333333;  
           border-width: 1px;  
           border-color: #666666;  
           border-collapse: collapse;  
      }  
      table th {  
           border-width: 1px;  
           padding: 8px;  
           border-style: solid;  
           border-color: #666666;  
      }  
      table td {  
           border-width: 1px;  
           padding: 8px;  
           border-style: solid;  
           border-color: #666666;  
      }  
      .footer { font-size: 10pt; }  
      </style>  
      <span>"  
      $logHTML = $log.ToString() -replace "`n","<br>"  
      $footer = "<p class=""footer"">[$($scriptpath)[$(get-date (get-item $scriptpath).lastwritetime -format G)] launched from $($env:computername) as $($env:username) at $($start)]</p></span>"  
      #Format the output  
      [string]$emailBody = [string]$body, [string]$logHTML, [string]$footer  
      #Send the report  
      #Needed to send without using default creditianls of the service or computer running the script  
      $s = New-Object System.Security.SecureString  
      $creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList "NT AUTHORITY\ANONYMOUS LOGON", $S  
      Send-MailMessage -To $emailTo -From $emailFrom -Subject "$($subjectTitle) - $($scriptName)" -BodyAsHtml $emailBody -SmtpServer $smtpServer -Credential $creds  
      } catch { [void]$log.appendline((("Error emailing log data - ")+$_.Exception.Message+(" ")+(get-date))); }  
 }  
 function createBackup {  
      Param(  
      [Parameter(Mandatory=$true)]  
      [string]$backupName,  
      [string]$backupRoot,  
      [string]$hostIP,  
      [string]$username,  
      [string]$password,  
      [string]$domain  
      ) #end param  
      $backupFile = "backup-$($backupName).tar.gz"  
      $createBackupURL = "https://$($hostIP)/index.cgi?name=$($backupName)&id=3-5&action=Create+Backup"  
      $getBackupURL = "https://$($hostIP)/backup/$($backupFile)"  
      #initiate webclient with ignoring untrusted SSL  
      $netAssembly = [Reflection.Assembly]::GetAssembly([System.Net.Configuration.SettingsSection])  
      if($netAssembly)  
      {  
           $bindingFlags = [Reflection.BindingFlags] "Static,GetProperty,NonPublic"  
           $settingsType = $netAssembly.GetType("System.Net.Configuration.SettingsSectionInternal")  
           $instance = $settingsType.InvokeMember("Section", $bindingFlags, $null, $null, @())  
           if($instance)  
           {  
                $bindingFlags = "NonPublic","Instance"  
                $useUnsafeHeaderParsingField = $settingsType.GetField("useUnsafeHeaderParsing", $bindingFlags)  
                if($useUnsafeHeaderParsingField)  
                {  
                 $useUnsafeHeaderParsingField.SetValue($instance, $true)  
                }  
           }  
      }  
      $webClient = new-object System.Net.webClient  
      $webClient.Credentials = new-object System.Net.NetworkCredential($username, $password, $domain)  
      [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}  
      #Attempt to create backup  
      try {  
           $webpage = $webClient.DownloadString($createBackupURL)  
      } catch { }  
      #Version  
      if (($webpage.Split("`n"))[36].contains("3.05")) {  
           #version 3.05  
           $lineChkBk = 94  
      } else {  
           #v3.03 or ???  
           $lineChkBk = 92  
      }  
      #Check created backup  
      if (($webpage.Split("`n"))[$lineChkBk].Contains("SUCCESS!")) {  
           $bkStatus = $true  
      } else {  
           #Check created backup error  
           $bkStatus = $false  
           $message = "Checking if backup was created failed`nHost Output:`n"+($webpage.Split("`n"))[$lineChkBk] + "`n" + ($webpage.Split("`n"))[93]  
      }  
      # last Pull backup details  
      #  
      #parse backup output   
      $content = (($webpage.Split("`n"))[$lineChkBk])  
      $content -match "<td>(?<file>backup.[A-Z0-9 _.%+-].gz)</td><td>(?<date>.*[0-9]+)</td>"  
      $files = (((($content -split "<tbody><tr>")[1] -replace "<script language=""javascript"">") -replace "<td>") -replace "</tr><tr>") -split "</td>"  
      $line = 0  
      #search for our backup  
      Do {  
           if ($files[$line] -eq $backupFile) {  
                $line  
                $backupFilename = $files[$line]  
                $line++  
                $line  
                $backupFiledate = $files[$line]  
           }  
           $line++  
      } while (!($files[$line] -eq $null))  
      #verify backup output parsed  
      if (test-path variable:backupFilename) {  
           $tmpDate= get-date ($backupFiledate.Substring(4,6)+", "+$backupFiledate.Substring(20,4)+", "+$backupFiledate.Substring(11,9))  
           #check backup creation date newer than start failed - +5 min for delta  
           if ((get-date $tmpDate.AddMinutes(+5)) -ge $start) {  
                #download the backup file  
                $tmpFilename = $backupRoot+(get-date $tmpDate -Format "yyyyMMdd_HHmmss-")+$backupFile  
                try { $webClient.DownloadFile($getBackupURL,$tmpFilename); } catch {}  
                #test file download  
                if (test-path -path $tmpFilename ) {  
                     $message = "Backup triggered and the backup file was saved to $($tmpFilename)"  
                     $bkStatus = $true  
                } else {  
                     #test file download failed  
                     $message ="Check $($tmpFilenam) failed - Backup file did not download"  
                     $bkStatus = $false  
                }  
           } else {  
                #check backup creation date newer than start failed  
                $message ="Backup date not newer than the start of this runtime"  
                $bkStatus = $false  
           }  
      } else {  
           #verify backup output parsed failed  
           $message ="Could not locate the backup filename on output for $($createBackupURL)"  
           $bkStatus = $false  
      }  
      #convert bkstatus to Success or Failure for readable output  
      if ($bkstatus) {  
           $bkstatus = "Success"  
           $status["Success"]++  
      } else {  
           $bkstatus = "Failure"  
           $status["Failure"]++  
      }  
      [void]$log.appendline(((" - Backup $($bkstatus): $($message) - ")+(get-date)))  
      if ($debug) { write-host "Backup $($bkstatus): $($message)"; }  
 }  
 function purgeOldFiles {  
      Param(  
      [Parameter(Mandatory=$true)]  
      [string]$root,  
      [string]$filename,  
      [int]$daysToKeep  
      ) #end param  
      Get-ChildItem "$($root)$($filename)" | ? {((get-date $_.LastWriteTime).AddDays($daysToKeep) -le (Get-date)) } | % {  
           [void]$log.appendline(((" - Purging Old File, Filename: $($_), Date: $($_.LastWriteTime) - ")+(get-date)))  
           if ($debug) { write-host "Purging Old File, Filename: $($_), Date: $($_.LastWriteTime)" }  
           remove-item $_  
      }  
 }  
 #run the backup jobs  
 foreach ($backup in $backupjobs.keys) {  
      createBackup -backupName $backupJobs[$backup]["backupName"] -backupRoot $backupJobs[$backup]["backupRoot"] -hostIP $backupJobs[$backup]["hostIP"] -username $backupJobs[$backup]["username"] -password $backupJobs[$backup]["password"] -domain $backupJobs[$backup]["domain"]   
 }  
 #purge old backup files  
 if ($purgeOldBackups) { purgeOldFiles -root $purgeRoot -filename "*.tar.gz" -daysToKeep $purgeDaysToKeep; }  
 [void]$log.appendline((("Ending Script - ")+(get-date)))  
 if ($debug) { write-host "Ending Script - $(get-date)" }  
 #update e-mail subject to add statuses  
 if ($status["Success"] -gt 0) {  
      if ($status["Failure"] -gt 0) {     $tmpOutput = "Failures: "+$status["Failure"]+", Successes: "+$status["Success"];}  
      else { $tmpOutput = "Successes: "+$status["Success"]; }  
 } else { $tmpOutput = "Failures: "+$status["Failure"] ;}  
 $subjectTitle = $subjectTitle -replace "%status%", $tmpOutput  
 if ($createEmail) { sendEmail; }  
 if ($createLog) { writeLog; }  
Formatted for web with http://codeformatter.blogspot.com/ 

Example Backup Job

Example Backup Folder
Email Results of Production