Wednesday, September 23, 2020

Modifying Citrix Hypervisor (Xenserver) Guest VM boot order from Command Line

 I recently had need to modify the bios boot order on a bunch of Citrix VMs. This required a dozen or so clicks through the Xencenter interface and I had 100+ machines to do it one. Not wanting to spend all day on it, I decided to script it. However I found only minimal information how to modify the boot settings on VMs through the command line. Here's what I found. 

Note: This is probably only valid for Bios boot type. If your VM is using the UEFI boot mode these instructions may not be valid, proceed with caution. 

 Checking Current Boot Settings

To see your VM's current boot setting, you'll need to find the VM's UUID, then use the vm-param-list command; I then use grep to find what I'm looking for, because the vm-param-list output is copious.

# xe vm-list name-label=myvm #case sensitive name

uuid ( RO) : 12345678-1234-1234-1234-123456789abc
name-label ( RW): myvm
power-state ( RO): running

# xe vm-param-list uuid=12345678-1234-1234-1234-123456789abc | grep HVM-boot
HVM-boot-policy ( RW): BIOS order
HVM-boot-params (MRW): firmware: bios; order: cdn

From this output we see the VM is currently set to BIOS boot (again, I haven't tested this with UEFI) and the order is "cdn". 

The boot order is defined as follows

  • c: HDD
  • d: DVD
  • n: Network 

So a boot order of 'cdn' is first hdd, then dvd, then network. where an order of 'nc' would be first network, then harddrive and no DVD option.

Modifying Boot Seetings

xe has a few different ways to modfy vm params, however I found only one way actually works for the HVM-boot-params. The 'xe vm-param-set' command I found does not work at all, causing duplicate entries which are ignored by the system. 

Instead we have to clear the param first, the use the add command to fill in the new settings.

# xe vm-param-clear uuid=12345678-1234-1234-1234-123456789abc param-name=HVM-boot-params
# xe vm-param-add uuid=12345678-1234-1234-1234-123456789abc param-name=HVM-boot-params order=c firmware=bios 


# xe vm-param-list uuid=12345678-1234-1234-1234-123456789abc | grep HVM-boot
HVM-boot-policy ( RW): BIOS order
HVM-boot-params (MRW): firmware: bios; order: c

Here I have set the only boot option to be the HDD, with no network or DVD boot. 

Scripting it all Together

As I mentioned at the top, I had a whole bunch of machines I needed to remove network and DVD boot from, so I wrote a quick bash script to handle it. This script takes a list of VM names (via a file 'names.txt') and looks up the uuid (via a grep that pulls only the uuid pattern) and automatically sends that to the clear/add commands.

#!/bin/bash

#Expected format of names.txt is one name per line. Remember names are case sensitive.
for line in `cat names.txt`; do
vmuuid=`xe vm-list name-label=$line params=uuid | grep -o -m1 '[0-9a-f]\{8\}-\([0-9a-f]\{4\}-\)\{3\}[0-9a-f]\{12\}'`
echo "$line , $vmuuid"
xe vm-param-clear uuid=$vmuuid param-name=HVM-boot-params
xe vm-param-add uuid=$vmuuid param-name=HVM-boot-params order=c firmware=bios
done;
Hope this helps.

Monday, December 11, 2017

ZFS on CentOS : Initial setup

CentOS install

Info on the hardware I'm using can be found here.

For this demo, I'm using a minimal CentOS install. All install options are otherwise default.

Post install run a full system update

#yum update

Installing ZFS

I'm following the instructions here from zfsonlinux, annotating with additional steps I had to use to get things working.

First we need to install two required repositories; the epel-release and zfsonlinux repos

# yum install epel-release
# yum install http://download.zfsonlinux.org/epel/zfs-release.el7_4.noarch.rpm 

These will give us access to the various non-standard packages we need to complete the install. Now install kerenl-devel and reboot, I find the reboot here helps prevent errors down the line (which I talk more about below).

# yum install kernel-devel
# reboot

Now install zfs, and reboot again (again this helps with errors)

# yum install zfs
# reboot
This should complete without errors (if you get an error about 'requires dkms >= x.x.x.x' make sure the epel-release repo installed/ is working correctly) Now Let's see if it works correcyly with a zpool status. We don't have any pools yet, so this won't generate any info, but it will throw an error if things didn't install correctly

# zpool status
no pools available

However if we do get an error, then we'll have to do a bit of troubleshooting. I've run through this process four times now, and it hasn't worked exactly the same way twice. Here's a few of the errors I ran into.

DKMS required version

As explain above. This error shows up during the install of zfs if the epel-release repo has not been correctly setup. zfs requires a newer version of dkms than is available through the standard repositories.

The ZFS modules are not loaded

The error comes up after running the 'zpool status' command. This error indicates that the kernel modules didn't install correctly during the zfs install. As mentioned before, I haven't found a precise cause of this but there's a few ways to work around it. First get the status of the modules:

# dkms status
There should be two different modules 'spl' and 'zfs'. These modules should be 'installed' however if they are just listed as 'added' try the following:

  • If both show installed, try using modprobe to load the module
    • modprobe zfs
  • If spl is listed as added, reboot the machine this will often allow that module to install. If spl doesn't install with a reboot, try to manually install it:
    • dkms install -m spl -v x.x.x
    • the version x.x.x will be listed in the 'dkms status' command 
  • if spl is installed, but zfs is just 'added', first try a reboot, if that doesn't work try a manual install
    • dkms install -m zfs -v x.x.x
      • version x.x.x will be listed in the 'dkms status' command
    • If manual install throws an error, run the manual install again
      • This did actually happen to me once, manual install failed, ran the exact same command again, then it worked.
    • If manual install continues to fail, we'll need to clear out the module and start over
      • dkms remove -m zfs -v x.x.x --all
      • dkms add -m zfs -v x.x.x
      • dkms install -m zfs -v x.x.x

ZFS on CentOS 7 : Preface and HW info

Preface

These are the notes on my adventure into getting a ZFS server running on centos 7; with the objective of creating a software-defined-storage solution without the hardware compatibility limitations of vendor offerings.

I am using a mixture of enterprise and consumer grade hardware to start, basically the spare stuff I have around the office.

Hardware info

  • Server
    • Dell Poweredge R610
    • 24GB Ram
    • 4x Gbs ethernet
    • HDD
      •  x2 10k SAS 300GB HDD
      • Raid-1 handled by Poweredge controller
      • These are for the OS,  ZFS will not touch these
    • SDD
      • x2 Sandisk SATA 32GB
      • Passed through Poweredge controller to be handled by ZFS
      • These will be used for various caches as I test different tuning options
    • SAS HBA connected to MD3200
  •  JBOD-ish Device
    • DELL MD3200
    • 10x 7.2K SAS 1.2TB HDD
    • Technically this is not, and cannot be, a JBOD. But I've set each drive in it's own disk group, so it presents itself more-or-less like a JBOD.
  • Other Hardware
    • 4 Additional R610 servers, running XenServer to use as clients to test virtualization performance.
    • Dell Force10 10GB switch. This is in use for other things, but should provide sufficient bandwidth since my hosts are limited to 1GB by their hardware 

>> CentOS install and ZFS install

Thursday, December 7, 2017

package: zfs-dkms requires dkms >= 2.2.0.3

Solution

First install EPEL release repo

sudo yum install epel-release

This should fix the initial install issue. However once the install is complete zfs may not work properly, you may see:

# zpool status
The ZFS modules are not loaded
Try running '/sbin/modprobe zfs' as root to load them

However, running modprobe results in the following
 
# modprobe zfs
modprobe: FATAL: Module zfs not found

Looking at DKMS you will see the modules are added, but never installed and attempting to install them fails. This seems to be a bug if you don't install epel-release before installing kernel-devel.
 
# dkms status
spl, 0.7.3: added
zfs, 0.7.3: added

# dkms install -m spl -v 0.7.3
Error ! echo
Your kernel headers for kernel 3.10.0.693.el7.x86_64 cannot be found at
/lib/modules/3.10.0-693.el7.x86_64/build or /lib/modules/3.10.0.693.el7.x86_64/source

A reboot seems to solve the spl module issue, but the zfs module still refuses to install 

#reboot
[truncated reboot output]
 ...
[login]
 ...
#dkms status
spl, 0.7.3 3.10.0-693.11.1.el7.x86_64, x86_64: installed
zfs, 0.7.3: added

# dkms install -m zfs -v 0.7.3
[truncating error message, but there's a lot of exit code status 2, failed to clean build area, unable to remove file messages]

However, then run the zfs install again and it works... for some reason? 

# dkms install -m zfs -v 0.7.3
 [truncating a really long install process output]
DKMS: install completed

# modprobe zfs
# zpool status
no pools available

This doesn't make much sense, but best guess is that the install fails initially (due to the epel-release not being installed) in a not-very-elegant way. This leaves behind junk that causes the install to fail in the future. Rebooting and manually running the install clears that junk. This is all conjecture, but I hope it helps someone!
 

Additional troubleshooting

I've run through this a few times now, and the error doesn't seem to resolve the same way everytime. Most recent time I did it, the restart/manual install didn't work for the zfs module (spl installed after a reboot as before). I had to remove the ZFS module and re-add it.


# dkms remove -m zfs -v 0.7.3 --all
[output from removal]

# dkms add -m zfs -v 0.7.3
[output from add]

# dkms install -m zfs -v 0.7.3
[and it should work now]

Tuesday, May 17, 2016

Winmgmt ResetRepository File Already Exists

Solution

This error comes up because the WMI repository has been reset too many times, and windows runs out of file names.

Under "C:\windows\system32\wbem\" you'll see a bunch of "repository.###" folders. These are the backups windows makes of the repository whenever you reset it. However this seems arbitrarily limited to three digits, so once it hits repository.999 it can no longer back the wmi repo up before the reset, so the reset fails.

The quick and dirty solution is to delete the various repository.### folders then try the reset again. Better solution is to find out why the repository is having to be reset so often and try to fix the underlying cause of the WMI corruption.

Full Story

I have a number of problems with WMI repositories becoming corrupt and requiring to be reset. I've never been able to track down the precise cause of these corruptions, so I've just been doing my best to keep things up and running.

Today I ran into a fun problem. I had a VM that wouldn't allow logins (or logins would take forever). Closer inspection saw that it had the telltale signs of WMI problems (1 CPU core maxed out, high memory usage). I rebooted to safe mode to try the reset, but the reset (winmgmt /resetrepository) failed with the error.

WMI repository reset failed. Windows cannot create a file if that file already exists

Or something along those lines. I forgot to write down the exact error before rebooting the machine, sorry. 

Further investigation found the hundreds of "repository.###" folders in the wbem directory. Deleting these folders allowed the reset to run correctly. VM rebooted to normal mode without issue.

Friday, July 24, 2015

Win2D First Impressions

Introduction

I like making little 2D games from time to time. There are a myriad of tools to do this, but as a Systems Admin I like working as close to the computer as I can without getting bogged down in pointers. So many tools like Unity, RPGmaker, etc. are too high level (pun) for me -- I like knowing what's going on in the code -- while using the Directx/Direct2D in c++ gets too frustrating with it's micromanaging minutia, lack of generic object lists, pointer resolution, and memory management.

So it's a struggle, and it's always bugged me that C# (My favorite mid-level language) doesn't have any official API for DirectX. Someone at Mircosoft seems to have been thinking the same thing as they're working on Win2D -- a c# implementation of Direct2D, which is supposed to bring at least the 2D portions of DirectX to native Windows applications. Previously I've used third party offerings (SFML, namely) to accomplish this.

As I write this introduction I am just getting started setting it up. So what follows will be my first-impressions look at the (Alpha, Beta?) package as of version --  0.0.22 at time of writing.

I am following the  quickstart and getting set-up guides on the github site. Using my work machine (windows 8.1, vs 2015 community RC) and my home machine (win 10 RC, vs2015 community RC).

Raw Notes

  •  There is no "blank app" listed under my Visual C# templates, hopefully the "Blank Solution" under Visual Studio Solutions works. Couldn't find it in the online templates either. 
  •  Error "no projects in this solution supported by nuget" so that's fun
  • Trying to add additional packages to VS install, see if that helps
  • Yes that worked. From the Visual studio installer I added everything under "Windows and Web Development" and now I have the blank app as an option. It's listed as "Blank App (Universal Windows 8.1) which isn't quite the same as the guide says, but it's closer
  • As always, visual studio is weird about network locations.
  • Have to get a developer license for this type of app. It's apparently free, so whatever.
  • Nuget works now
  • Getting "Cannot Create an instance of "Canvas Control"" when following step 4 in quickstart
    • Specified module cannot be found 0x8007007E
    • appears to happen for all canvas modules
    • Switched Processor target under Build > Configuration Manager from 'any CPU' to 'x64' this seems to have cleared up the issue (did have to compile before the warning went away)
    • The cpu target thing was mentioned in the instructions, I just missed it.
  •  Have drawn some text and some shapes, neat
  • getting into animation
    • CanvasAnimatedControl is kind of neat, appears to handle the looping for you for drawing. Just have to say draw these things, then it redraws as those things change.
  •  trying to figure out the precise commands by adding a button to the mix
    • Hurray I have a generic object list I can add/remove things from that automatically draws to the screen.
  • Method of loading images is a bit wonky, but ultimately works
  • Wonder if I can get this working in a non win8 style app. Just like, a wpf app or something.

Conclusion

So there's that. Not really what I'm looking for since it only supports Windows apps. Still it seems nice, really straight forward and what have you. If I were looking to make a windows app it would certainly appeal.

Maybe one day Microsoft will make an official supported C# wrapper for DirectX. For now I think I'm going to try SharpDX and see how that goes.

Friday, July 17, 2015

Code Dump: Get-UserProfile / Remove-UserProfile

function Get-UserProfile{
        <#
        .SYNOPSIS
            Use WMI to query a computer about local profiles on the machine
        .Description
            This Fucntion is used to get information about local profiles on a computer. This will return information about any local profile, including the local cache of the domain accounts. By default returns the SID, Local path, and Last Use Time of the account; the -verbose flag can be used to return additional information.
        .PARAMETER UserID
            UserID to search for. If left blank will default to all users. By default UserID must match exactly, but you can use the wildcard '%' to perform more general seraches
        .PARAMETER Computer
            Computer to query for user accounts. Leaving Blank will default to 'localhost'. 
        .PARAMETER ExcludeSystemAccounts
            Filters out System accounts (e.g. System, Network Service). This is done by looking at the 'special' property, which does not filter out users non-windows programs may create.
        .PARAMETER OnlyLoaded
            Setting this parameter shows only profiles that are currently in Use -- Combine with -ExcludeSystemAccounts and you can get a pretty good idea of who is currently logged into a machine.
        .PARAMETER ExcludeLoaded
            Returns only user profiles that are not currently in use. This is useful if you need to clear out profiles.
        .PARAMETER Verbose
            Returns Full user porfile data, rather than the default SID,LocalPath,LastUseTime
        .PARAMETER OlderThan
            Filter Results based on datetime. This requires a datetime object
        .Example
            Get-UserProfile -UserID MyUser
            Basic usage to see if the user "MyUser" exists on the local machine.
        .Example
            Get-UserProfile -Computer RDSServ1.mydomain.com
            Lists all user Profiles from remote computer "RDSServ1.mydomain.com" 
        .Example
            Get-UserProfile -Computer RDSServ1.mydomain.com -ExcludeSystemAccounts -OnlyLoaded
            Lists non-system user profiles from remote computer currently marked as loaded. This gives a pretty good idea of who is currently logged into a remote machine.
        .Example
            Get-UserProfile -OlderThan $((get-date).adddays(-14))
            Lists user profiles that have not been used on the localhost in 14 days.
        .Notes
            Author: Keith Ballou
            Date: Oct 15, 2014

            This Script Relies on Convert-UTCtoDateTime -- A function for converting UTC strings to DateTime Objects


    #>
    [CmdletBinding()] 
      param( 
     [Parameter(Mandatory=$False)][string]$UserID="%",
     [Parameter(Mandatory=$False)][string]$Computer="LocalHost",
     [Parameter(Mandatory=$False)][switch]$ExcludeSystemAccounts,
     [Parameter(Mandatory=$False)][switch]$OnlyLoaded,
     [Parameter(Mandatory=$False)][switch]$ExcludeLoaded,
     [Parameter(Mandatory=$False)][datetime]$OlderThan   
     
    )
if(!(Get-Command Convert-UTCtoDateTime -ErrorAction SilentlyContinue)){
    write-host -BackgroundColor "Black" -ForegroundColor "Red" "################################################################################"
    write-host -BackgroundColor "Black" -ForegroundColor "Red" "#                                                                               "
    write-host -BackgroundColor "Black" -ForegroundColor "Red" "This Program Requires cmdlet ""Convert-UTCtoDateTime""                          "
    write-host -BackgroundColor "Black" -ForegroundColor "Red" "Find it here:                                                                   "
    write-host -BackgroundColor "Black" -ForegroundColor "Red" "http://pastebin.com/SSKJ4bwt                                                    "
    write-host -BackgroundColor "Black" -ForegroundColor "Red" "#                                                                               "
    write-host -BackgroundColor "Black" -ForegroundColor "Red" "################################################################################"
    break;
}
if($Computer.ToLower() -eq "localhost"){
    
    
    $Return = Get-WmiObject -Query "Select * from win32_userprofile where LocalPath like '%\\$UserID'" 
    

}
else{
    $Return = get-wmiobject -ComputerName $Computer -Query "Select * from win32_userprofile where LocalPath like '%\\$UserID'" 
}

#Filter System Accounts
if($ExcludeSystemAccounts){
    $Return = $Return | Where-Object -Property Special -eq $False
}
#Filter out Loaded Accounts
if($ExcludeLoaded){
    $Return = $Return | Where-Object -Property Loaded -eq $False
}
#Filter otherthan loaded accounts
if($OnlyLoaded){
    $Return = $Return | Where-Object -Property Loaded -eq $True
}

#Filter on lastusetime
if([bool]$OlderThan){
$Return | Where-Object -property LastUseTime -eq $Null | % {Write-Host -BackgroundColor "Black" -ForegroundColor "Yellow" $_.LocalPath " Has no 'LastUseTime', omitting" }
$Return = $Return | Where-Object -property LastUseTime -ne $Null
$Return = $Return | Where-Object {$(Convert-UTCtoDateTime $_.LastUseTime -ToLocal) -lt $OlderThan }
}

if($PSBoundParameters['Verbose'])
{
Write-Output $Return
}
else{
 Write-Output $Return | Select SID,LocalPath,@{Label="Last Use Time";Expression={Convert-UTCtoDateTime $_.LastUseTime -ToLocal}}    
}

}


Function Remove-UserProfile{
            <#
        .SYNOPSIS
            Removes User Profiles from a machine via WMI
        .Description
            This Cmdlet is used to remove user profiles from a machine when it is inconveinient to do so from the System menu. The script relies on Cmdlet Get-UserProfile. It is especially userful on Terminal Server/RDS machines where opening the system menu to delete user profiles can take hours (because reasons, I suppose). 
        .PARAMETER UserID
            UserID to search for. Unlike Get-UserProfile, this cannot be left blank. If you intentionally want to remove all user profiles, or need to select multiple, you can use WMIs limited regex. Check this Link for more information: http://blogs.technet.com/b/heyscriptingguy/archive/2012/07/13/use-the-like-operator-to-simplify-your-wql-queries.aspx
        .PARAMETER Computer
            Computer to delete user accounts from. Leaving Blank will default to 'localhost'. 
        .PARAMETER Batch
            This Flag will suppress confirmation dialogs, as well change console output to the more redirect friendly Write-Output, rather than the Write-Host it otherwise uses.
        .PARAMETER OlderThan
            Filter user accounts based on last use time. Remember that this Cmdlet does not assume 'all users', so you must specify -UserID %. 
        .Example
            Remove-UserProfile myuser remotemachine.mydomain.com
            Remove Users using positional parameters
        .Example
            Remove-UserProfile -UserID myuser -Computer remotemachine.mydomain.com
            remove users specifying with flags
        .Example
            Get-Content machinelist.txt | % {Remove-UserProfile -User myuser -Computer $_ -Batch} >> C:\Temp\UserRemoval.log
            Remove a user from a list of machines using the batch mode, seding output to a log file
        .Example
            Remove-UserProfile -OlderThan $((get-date).adddays(-14)) -UserID %
            Remove all user accounts that haven't been used in 14 days
            The '%' here is the WMI regex for anything (analogous to '*' (or '.*') in most regex) -- it is necessary because this Cmdlet never assumes all users

        .Notes
            Author: Keith Ballou
            Date: Oct 16, 2014
         #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$True)][string]$UserID,
        [Parameter(Mandatory=$False)][string]$Computer="LocalHost",
        [Parameter(Mandatory=$False)][datetime]$OlderThan=(get-date).adddays(1),
        [Parameter(Mandatory=$False)][switch]$Batch

    )

    #Make Sure Necessary Cmdlets Exist
    if(!(Get-Command Get-UserProfile -ErrorAction SilentlyContinue)){
    write-host -BackgroundColor "Black" -ForegroundColor "Red" "################################################################################"
    write-host -BackgroundColor "Black" -ForegroundColor "Red" "#                                                                               "
    write-host -BackgroundColor "Black" -ForegroundColor "Red" "This Program Requires cmdlet ""Get-UserProfile""                                "
    write-host -BackgroundColor "Black" -ForegroundColor "Red" "Find it here:                                                                   "
    write-host -BackgroundColor "Black" -ForegroundColor "Red" "http://pastebin.com/wvUDki7p                                                    "
    write-host -BackgroundColor "Black" -ForegroundColor "Red" "#                                                                               "
    write-host -BackgroundColor "Black" -ForegroundColor "Red" "################################################################################"
    break;
    }

    #to simplify query, if OlderThan was not specified, assume anything earlier than right now (plus a day to account for timezones, rounding, etc)
    #if(![bool]$OlderThan){
     #   $OlderThan = (get-date).adddays(1)
    #}

    #This Part relies on another of my Cmdlets "get-UserPorfile" to simplify the code a bit
    #This Could be substituted with: $ProfileList = Get-WmiObject -Computer $Computer -Query "Select * From Win32_UserProfile where LocalPath like \\$UserID"
    #..............................: $ProfileList = $ProfileList | Where-Object -Property Special -eq $False
    #Additional logic may have to be added if WMI doesn't like 'localhost' as a ComputerName. Seems to work ok for me, but I wouldn't count on it.
    #Get_UserProfile includes this logic
    $ProfileList = Get-UserProfile -Verbose -UserID $UserID -Computer $Computer -ExcludeSystemAccounts -OlderThan $OlderThan


    #If no Users were found, exit
    if(!$ProfileList){
        Write-Warning "NO USER PROFILES WERE FOUND"
        RETURN;
    }

    #Confirmation Dialog if -Batch is not set
    if(!$Batch){
        Write-Warning "ABOUT TO REMOVE THE FOLLOWING USER ACCOUNTS"
        Foreach($User in $ProfileList){
            $User | Select SID,LocalPath,@{Label="Last Use Time";Expression={Convert-UTCtoDateTime $_.LastUseTime -ToLocal}}
        }
        $Title = "PROCEED?"
        $Message = "ARE YOU SURE YOU WANT TO REMOVE THE LISTED USER ACCOUNTS?"
        $Yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes","Removes User Accounts"
        $No = New-Object System.Management.Automation.Host.ChoiceDescription "&No","Exits Script, No Changes Will be Made"
        $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
        $result = $host.ui.PromptForChoice($title, $message, $options, 1) 
        switch ($result)
        {
            0 {}
            1 {return;}
        }

    }

    #Remove Users provided they are not currently Loaded
    Foreach($User in $ProfileList){
        if($User.Loaded){
            if(!$Batch){
            Write-Host -BackgroundColor "Black" -ForegroundColor "Red" "User Account " $User.LocalPath "is Currently in user on" $Computer ":`tSkipping"
            }
            else{
            Write-Output "User $($User.LocalPath) on $($Computer) was in use and could not be removed"
            }
            continue;
        }
        if(!$Batch){
        Write-Host -BackgroundColor "Blue" -ForegroundColor "Green" "Removing User $($UserID.LocalPath) from $($Computer)"
        }
        else{
        Echo "Deleting $($User.LocalPath) from $($Computer)"
        }
        $User.delete()


    }

}

function Get-SNMPWalk{

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$True)][string]$IP,
        [Parameter(Mandatory=$False)][int]$SNMPVersion=2,
        [Parameter(Mandatory=$False)][int]$Port=161,
        [Parameter(Mandatory=$False)][int]$TimeOut=3000,
        [Parameter(Mandatory=$False)][string]$Community="public",
        [Parameter(Mandatory=$False)][string]$OIDStart=".1.3.6.1.2.1.1.1"
    )

[reflection.assembly]::LoadFrom( (Resolve-Path ".\SharpSnmpLib.dll")) | Out-Null

if($SNMPVersion -gt 2 -or $SNMPVersion -lt 1)
{
    Write-Error "Invalid SNMP Version Number, This script supports Versions 1 and 2"
    return;
}

$OIDObject = New-Object Lextm.SharpSnmpLib.ObjectIdentifier ($OIDStart)
#if($SNMPVersion < 3)
#{
#    $results = New-GenericObject System.Collections.Generic.List Lextm.SharpSnmpLib.Variable
#else
#{
    $results = New-Object 'System.Collections.Generic.List[Lextm.SharpSnmpLib.Variable]'
#}

$IPObject=[System.Net.IPAddress]::Parse($IP)
$Server=New-Object System.Net.IpEndPoint($IPObject,$Port)

switch($SNMPVersion)
{
    1
    {
        $SNMPVersionObject = [Lextm.SharpSnmpLib.VersionCode]::V1
    }
    2
    {
        $SNMPVersionObject = [Lextm.SharpSnmpLib.VersionCode]::V2
    }
    #3
    #{
    #    $SNMPVersionObject = [Lextm.SharpSnmpLib.VersionCode]::V3
    #}
}

$WalkMode = [Lextm.SharpSnmpLib.Messaging.WalkMode]::WithinSubtree

#try
#{
    [Lextm.SharpSnmpLib.Messaging.Messenger]::Walk($SNMPVersionObject,$Server,$Community,$OIDObject,$results,$TimeOut,$WalkMode) | Out-Null
#}
#catch
#{
    #Write-Host "SNMP Walk error: $_"
#     return;    
#}
    $res = @()
    foreach ($var in $results) {
        $line = "" | Select IP,OID, Data
        $line.IP = $IP
        $line.OID = $var.Id.ToString()
        $line.Data = $var.Data.ToString()
        $res += $line
    }
 
    $res
}


function Convert-UTCtoDateTime{


    #Parameter Binding
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$True,Position=1)][string]$UTC,
        [Parameter(Mandatory=$false)][switch]$ToLocal
        )

    #Breakout the various portions of the time with substring
    #This is very inelegant, and UTC 
    $yyyy = $UTC.substring(0,4)
    $M = $UTC.substring(4,2)
    $dd = $UTC.substring(6,2)
    $hh = $UTC.substring(8,2)
    $mm = $UTC.substring(10,2)
    $ss = $UTC.substring(12,2)
    $fff = $UTC.substring(15,3)
    $zzz = $UTC.substring(22,3)

    #If local, add the UTC offset returned by get-date
    if($ToLocal){
    (get-date -Year $yyyy -Month $M -Day $dd -Hour $hh -Minute $mm -Second $ss -Millisecond $fff) + (get-date -format "zzz")
    }
    #else just return the UTC time
    else{
    get-date -Year $yyyy -Month $M -Day $dd -Hour $hh -Minute $mm -Second $ss -Millisecond $fff
    }
}