Traverse.psm1

#Requires -Version 3

function Connect-TraverseBVE {
<#
.SYNOPSIS
 Connects to a Traverse BVE system with the Web Services API enabled.
 
.PARAMETER Hostname
The DNS name or IP address of the Traverse BSM system
 
.PARAMETER Credential
The username and password needed to access the system in secure PSCredential format.
 
.PARAMETER Force
Create a new session even if one already exists
 
.PARAMETER NoREST
Skips the connection to the REST API
 
.PARAMETER RESTSessionPassTHru
Pass the REST session object to the pipeline. Useful if you want to work with multiple sessions simultaneously
 
.PARAMETER WSSessionPassThru
Pass the SOAP session object to the pipeline. Useful if you want to work with multiple sessions simultaneously
 
#>


param (
    [Parameter(Mandatory=$true)][String]$Hostname,
    [Parameter(Mandatory=$true)][PSCredential]$Credential = (get-credential -message "Enter your Traverse Username and Password"),
    [Switch]$Force,
    [Switch]$NoREST,
    [Switch]$RESTSessionPassThru,
    [Switch]$WSSessionPassThru
) # Param

if (!$NoREST) {
    #Check for existing session
    if ($global:TraverseSessionREST -and !$force) {write-warning "You already have a Traverse REST session. Use the -force parrameter if you want to connect to a different one or use a different username";return}

    #Log in using Credentials
    $RESTLoginURI = "https://$Hostname/api/rest/command/login?" + $Credential.GetNetworkCredential().UserName + "/" + $Credential.GetNetworkCredential().Password
    $loginResult = Invoke-RestMethod -sessionvariable TraverseSessionREST -Uri $RESTLoginURI
    if ($loginresult -notmatch "OK") {throw "The connection failed to $Hostname. Reason: $loginResult"}
    $Global:TraverseSessionREST = $TraverseSessionREST
    write-verbose "Connected to $Hostname BVE as $($Credential.GetNetworkCredential().Username) using REST API"
    $TraverseSessionREST
}

if ($global:TraverseSession -and !$force) {write-warning "You are already logged into Traverse. Use the -force parameter if you want to connect to a different one or use a different username";return} 

#Workaround for bug with new-webserviceproxy (http://www.sqlmusings.com/2012/02/04/resolving-ssrs-and-powershell-new-webserviceproxy-namespace-issue/)
$TraverseBSMLoginWS = (new-webserviceproxy -uri "https://$($hostname)/api/soap/login?wsdl" -ErrorAction stop)
$TraverseBSMLoginNS = $TraverseBSMLoginWS.gettype().namespace

#Create the login request and unpack the password from the encrypted credentials
$loginRequest = new-object ($TraverseBSMLoginNS + '.loginRequest')
$loginRequest.username = $credential.GetNetworkCredential().Username
$loginRequest.password = $credential.GetNetworkCredential().Password

$loginResult = $TraverseBSMLoginWS.login($loginRequest)

if (!$loginResult.success) {throw "The connection failed to $Hostname. Reason: Error $($loginresult.errorcode) $($loginresult.errormessage)"}

set-variable -name TraverseSession -value $loginresult -scope global
set-variable -name TraverseHostname -value $hostname -scope global
write-verbose "Connected to $hostname BVE as $($loginrequest.username) using SOAP API"
$LoginResult

} #Connect-TraverseBSM

function Get-TraverseDevice {
#.Description
#Gets all listed Traverse devices.
#TODO: Add SearchCriteria

param (
) # Param

#Exit if not connected
if (!$global:TraverseSession) {write-warning "You are not connected to a Traverse BSM system. Use Connect-TraverseBSM first";return}

#Connect to the Device Web Service
$TraverseBSMDeviceWS = (new-webserviceproxy -uri "https://$($TraverseHostname)/api/soap/device?wsdl" -ErrorAction stop)
$TraverseBSMDeviceNS = $TraverseBSMDeviceWS.gettype().namespace

#Create device request
$DeviceRequest = new-object ($TraverseBSMDeviceNS + '.deviceStatusesRequest')
$DeviceRequest.sessionid = $TraverseSession.result.sessionid

$DeviceResult = $TraverseBSMDeviceWS.getStatuses($DeviceRequest)

if (!$DeviceResult.success) {throw "The connection failed to $TraverseHostname. Reason: Error $($DeviceResult.errorcode) $($DeviceResult.errormessage)"}

return $DeviceResult.result.devices
} #Get-TraverseDevice

workflow Get-TraverseWindowsServerExtendedInfo {
<#
.SYNOPSIS
Gets extended information about a Traverse Windows Device such as BMC and Serial Number, and adds an ExtendedInfo property to the device object
 
.PARAMETER TraverseDeviceObject
One or more Traverse Device Objects obtained via Get-TraverseDevice
 
.PARAMETER ThrottleLimit
How many devices to process concurrently if multiple devices are specified. Default is 5
 
.PARAMETER GetHPInfo
If enabled, system will try additional techniques to get HP iLO BMC information. Requires the HPILOStatus module and PSExec from Sysinternals to be present in the path.
 
#>


param(
$TraverseDeviceObject,
[int]$ThrottleLimit = 5,
[switch]$UseHPONCFG
)

foreach -parallel -throttle $ThrottleLimit ($device in $TraverseDeviceObject) {
    inlinescript{
        $device = $USING:Device
        $deviceAddress = $device.deviceaddress
        #Construct the result hashtable
        $InfoResult = @{}
        
        #Get the system Hostname, Make, Model, and Serial Number Information
        write-progress -Activity "Get Traverse Windows Extended Info" -CurrentOperation "$($devices.DeviceName): Querying WMI Information"
        $deviceComputerSystemInfo = Get-WMIObject win32_computersystem -computername $deviceAddress -erroraction stop
        $deviceBIOSInfo = Get-WMIObject Win32_bios -computername $deviceAddress -erroraction stop
        if ($deviceComputerSystemInfo -and $deviceBIOSInfo) {
            if ($deviceComputerSystemInfo.model -match "Virtual") {
                $infoResult.isVirtual = $true
            }
            else {
                $infoResult.Manufacturer = $deviceComputerSystemInfo.Manufacturer.Trim()
                $infoResult.Model = $deviceComputerSystemInfo.Model.Trim()
                $infoResult.SerialNumber = $deviceBIOSInfo.SerialNumber.Trim()
                $infoResult.isVirtual = $false
            } #Else
        } #If

        #Get BMC IP Information
        $BMCResult = get-wmibmcipaddress $deviceAddress
        if ($BMCResult) {$InfoResult.BMCIPAddress = $BMCResult.BMCIPAddress}


        #Attach the Extended Attribute to the device and return it
        $device | Add-Member -Name "extendedInfo" -MemberType NoteProperty -Value $InfoResult
        return $device
    } #InlineScript
} #Foreach -Parallel
} #Get-TraverseExtendedInfo


function Set-TraverseDevice {
<#
.SYNOPSIS
Update the configuration of a device. Currently this only supports some basic descriptive information.
 
.NOTES
This is a wrapper around the Device.Update FlexAPI command http://help.kaseya.com/webhelp/EN/tv/7000000/dev/index.asp#30181.htm
Supports Common Parameters -Whatif and -Confirm
 
.PARAMETER TraverseDevice
A Traverse Device, represented as a Traverse deviceStatus object.
 
.PARAMETER NewDeviceName
Rename a device. THIS IS DANGEROUS IF USED ON THE PIPELINE AND YOU CAN ACCIDENTALLY SET A LOT OF DEVICES TO THE SAME NAME. Please be careful with this parameter
 
.EXAMPLE
Set the description for all devices to "this is a test" (remove the -whatif to do it for real)
 
PS C:\> Get-TraverseDevice | Set-TraverseDevice -Comment
 
#>


[CmdletBinding(SupportsShouldProcess)]  

param (
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]$TraverseDevice,
    [Alias("Description")][String]$Comment,
    [String]$Tag5,
    [String]$NewDeviceName
)

begin {
    if (!$global:TraverseSessionREST) {write-warning "You are not connected to a Traverse BSM system via REST. Use Connect-TraverseBSM first";return}
    
    #Populate the update information based on what was provided
    $setDeviceParams = [ordered]@{}
    if ($Comment) {$setDeviceParams.Comment = $Comment.trim()}
    if ($NewDeviceName) {$setDeviceParams.DeviceName = $newDeviceName.trim()}

    #Tag5 might store extended properties as XML. If so, replace XML brackets with benign character so that it is not flagged by API
    if ($Tag5) {
        #Strip out any curly brackets and carriage returns. Not allowed for extended properties anyways
        $Tag5 = $Tag5.replace("`r",'').replace("`n",'').replace("`{",'').replace("`}",'').trim()
        #Tag5 might store extended properties as XML. If so, replace XML brackets with benign curly brackets so that it is not flagged by API
        $setDeviceParams.Tag5 = $Tag5.replace('<','{').replace('>','}')
    }


    #Exit if nothing was specified
    if ($setDeviceParams.count -eq 0) {throw "No parameters for the device has been specified to be set. Use the arguments to add information to set on the device. See the help for examples."}
}
process {
    foreach ($Device in $TraverseDevice) {
        $setDeviceParams.DeviceSerial = $Device.serialnumber

        if ($PSCmdlet.ShouldProcess("$($Device.devicename) `($($Device.serialnumber)`)","Setting Traverse Device Properties")) {
            $uriSetDevice = "https://$TraverseHostname/api/rest/command/devices.update"
            $resultSetDevice = invoke-restmethod -WebSession $TraverseSessionREST -uri $uriSetDevice -body $setDeviceParams

            if (!$resultSetDevice) {$resultSetDevice = "Error: No Response from Traverse BVE"}

            #Return a Result Object
            $resultSetDeviceProperty = [ordered]@{}
            $resultSetDeviceProperty.TraverseDeviceName=$TraverseDevice.DeviceName
            $resultSetDeviceProperty.TraverseDeviceSerial=$setDeviceParams.DeviceSerial
            $resultSetDeviceProperty.Result=$ResultSetDevice
            new-object PSObject -property $resultSetDeviceProperty

        }#If
    }#Foreach
}

}

function Update-TraverseWindowsExtendedInfo {
<#
.SYNOPSIS
Queries Customer Windows Servers and updates their Traverse Extended Info (stored in Tag5)
 
.PARAM TraverseAccountName
Name of the customer account to update. The computer system must have network access to the systems to be updated. Must match exactly for safety
 
 
 
.NOTES
THIS ASSUMES LOGGED IN USER HAS RIGHTS TO THE TARGET SYSTEM. TODO: Allow for alternate credentials
TODO: Add full device search criteria
 
.EXAMPLE
Update all Systems for Customer "Contoso Corp"
PS C:\> Update-TraverseWindowsExtendedInfo -TraverseAccountName "Contoso Corp"
#>


param (
[String]$TraverseAccountName,
$credential = $seasonsCredential
)

$devices = Get-TraverseDevice | where {$_.accountname -eq $TraverseAccountName}
$windevices = $devices | where {$PSItem.devicetypestr -match "Windows Server"}

$resultExtendedInfo = get-TraverseWindowsServerExtendedInfo $windevices

foreach ($result in $resultExtendedInfo) {
    if ($result.extendedInfo) {
        $xmlExtendedInfo = ($result.extendedinfo | convert-hashtabletoxml -root DeviceExtendedInfo).OuterXML.replace("`n","")
        set-traversedevice $result -Tag5 $xmlExtendedInfo
    }
}

}