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

Monday, December 7, 2015

Abandon Ship!


At my work, we've been using Hyper-V as our virtualization hypervisor since 2008. We started down a new path of hypervisors since the beginning of this year. This last summer we acted and have been switching our virtual hosts to vSphere 6.0. The processes of converting virtual machines has been very uneventful - 93 conversions with only 3 resulting in post migration errors. We still have 16 VMs left to convert, but we should be done by January 2016 and Hyper-V will be a distant hypervisor memory.

The point of this post is just to explain what worked for us to perform these Virtual Machine to Virtual Machine (V2V) conversions.

Step 1: Shutdown the virtual machine.

Step 2: Backup the Hyper-V Virtual Machine - CYA

Step 3: Convert the VHD/VHDX files to VMDK files. For this, I preferred using StarWinds Converter Tool. VMware recommends using the VMware vCenter Converter Standalone. (Mainly because I was able to wrap the CLI tool into PowerShell - see below.)

Step 4: Create a new template Virtual Machine in vCenter matching the Hyper-V machine.


Step 5: Upload the converted VMDK files to your vSphere Datastore.
Connecting to vCenter with the old vSphere Client worked best for us.

Step 6: Clone the VMDK disks to ensure they're in the correct datastore format. (See http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=2036572 for detailed steps)
This step was added do the StarWind Converter tool putting thin provisioned disks multiextent format, which can cause the virtual machines to crash when snapshot'd - particularly during Step 9 for us when Veeam Backup and Recovery would kick off a backup. I'm not sure what different scenarios that the StarWind Converter will not cause this problem, but after I lost one of our Active Directory (AD) servers to this bug, this step was added and no problems since. Unfortunately, that AD server was hours to repair. Just do this step.
If you use the VMware Standalone Converter, you should be safe to pass

Step 7: Attach VMDK disks.

Step 8: Turn on new VM and finish post configuration - Linux: As long as the MAC address matched the old VM, nothing; Windows: Activate Licensing, Reconfigure IP Addresses, etc.

Step 9: Verify the system is still working.

Step 10: Backup the new VMware virtual machine.

Step 11: Remove the old Hyper-V virtual machine.

Important take away's I've learned:

  • Ensure you process has plan B, C, D's, etc...
  • Backup, Backup, Backup
  • Did I mention backup?
  • Be cautious when converting Active Directory Servers and any database (SQL) systems. If you can avoid V2V of these types of systems, do avoid them.
  • Get 10Gbps networking. Unless you like to sit and wait for files to backup/copy/upload, make sure your network can take the hit.

Also, I've created a PowerShell cmdlet to automate and speed up step 3.

https://github.com/cajeeper/PowerShell/blob/master/Convert-VHDtoVMDK.ps1

<#  
 .SYNOPSIS  
  Function to convert streamline conversion of multiple VHD/VHDX files from one directory to VMware VMDK files in a different directory.  
    
 .DESCRIPTION
  The first VHD/VHDX found is assumed to IDE and all sub-sequential be SCSI adapters.
  This function requires the use the StarWinds V2V console conversion tool in order to make this function.
  Also, Windows 8 / Server 2012 or above is required for the StarWinds tool to convert any VHDX files.
 
 .NOTES   
  Author   : Justin Bennett   
  Date     : 2015-12-07
  Contact  : http://www.allthingstechie.net
  Revision : v1.1 
  Changes  : v1.0 Original
    v1.1 Added starwindsConvert, vmdkAdapterType, and vmdkType parameters
   
 .PARAMETER convertFromDir  
  Name of directory location containing VHD/VHDX files
 .PARAMETER convertFromTo
  Name of directory location where VMDK files will be deposited
  
 .PARAMETER vmdkAdapterType
  Set vmdk disk adapter to IDE, SCSI, or FirstIDE to first disk IDE with subsequential vmdk disks set to SCSI
  
 .PARAMETER vmdkType
  Set vmdk disk type to VMDK_F - VMWare pre-allocated image, VMDK_S - VMWare growable image,
 VMDK_SO - VMWare stream-optimized image, or VMDK_VMFS - VMWare ESX server image.
 
 .PARAMETER starwindsConvert
  File path of the StarWindows Converter Executable, StarV2Vc.exe 
 
 .EXAMPLE  
  C:\PS> #Convert files in C:\DirA to C:\DirB
  C:\PS> Convert-VHDtoVMDK -convertFromDir 'C:\DirA' -convertToDir 'C:\DirB'
 #> 
function Convert-VHDtoVMDK {
     [CmdletBinding()]  
      param (  
           [parameter(Mandatory=$True)] [ValidateScript({Test-Path $_})] [string] $convertFromDir,
     [parameter(Mandatory=$True)] [ValidateScript({Test-Path $_})] [string] $convertToDir,
     [parameter(Mandatory=$False)] [ValidateSet("IDE","SCSI","FirstIDE")] [string] $vmdkAdapterType = "SCSI",
     [parameter(Mandatory=$False)] [ValidateSet("VMDK_F","VMDK_S","VMDK_SO","VMDK_VMFS")] [string] $vmdkType = "VMDK_F",
     [parameter(Mandatory=$False)] [ValidateScript({Test-Path $_})] [string] $starwindsConvert = 'C:\Program Files (x86)\StarWind Software\StarWind V2V Image Converter\StarV2Vc.exe'
     )
 
 #initial vmdk set to 
 if($vmdkAdapterType -eq "SCSI") { $vmdkadtype = "SCSI" } else { $vmdkadtype = "IDE" }
 $vmdkType = "vmdk_s"
 
 $files = Get-ChildItem -Path $convertFromDir -Filter *.vhd*
 
 foreach ($file in $files) {
  $i++
  $start = get-date
  $run = "`& `"$($starwindsConvert)`" if=`"$($file.fullname)`" of=`"$($convertToDir)\$($file.basename).vmdk`" ot=$($vmdkType) vmdktype=$($vmdkadtype)"
  #run conversion
  Write-Progress -Activity "Converting VHD(X) to VMDK: From $($convertFromDir), To $($convertToDir)" -Status "File $($i)/$($files.count): $($file.name), Started at $(get-date -Format g $start)"
  try { $output = Invoke-Expression $run } catch { $output = "Failed to convert file $($file.name), Error $($_)"; write-warning $output; }
  $end = get-date
  New-Object PSCustomObject -Property ([ordered]@{
   "File"= $file.name
   "Start"= $start
   "RunCMD"= $run
   "Output"= $output
   "End"= $end
  })
  
  #subsequential vmdk set to SCSI if FirstIDE set
  if($vmdkAdapterType -eq "FirstIDE") { $vmdkadtype = "SCSI" }
 }
 
 if(!($i -ge 1)) { write-warning "No files found in $($convertFromDir)" }
}
Formatted for web with http://codeformatter.blogspot.com/ 

Screenshot of Convert-VHDtoVMDK

References:
StarWind V2V Converter - https://www.starwindsoftware.com/converter
VMware vCenter Converter Standalone - https://my.vmware.com/web/vmware/evalcenter?p=converter
Powering on a virtual machine on an upgraded host fails with the error: File [VMFS volume] VM-name/VM-name.vmdk was not found (2036572) - http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=2036572

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...