Traverse-Helper.psm1

#####################################
#####################################
##### IMPORTED HELPER FUNCTIONS #####
#####################################
#####################################
#These are functions brought from other modules or systems to remove dependencies and make this module self-sufficient

function Get-WMIBMCIPAddress {
<#
.SYNOPSIS
Accesses WMI to obtain the BMC IP Address of the device
 
.NOTES
Based on script by Michael Albert (http://michlstechblog.info/blog/windows-read-the-ip-address-of-a-bmc-board/)
 
.PARAMETER ComputerName
Name of the Windows Computer to check for a BMC
 
#>


param (
[Parameter(Mandatory=$true,ValueFromPipeline=$true)][String[]]$ComputerName
)

Begin {
    # Some constants
    # <a title="Microsoft In Band Management" href="http://gallery.technet.microsoft.com/scriptcenter/In-Band-Management-using-88e221b8" target="_blank">Source</a>
    [byte]$BMCResponderAddress = 0x20
    [byte]$GetLANInfoCmd = 0x02
    [byte]$GetChannelInfoCmd = 0x42
    [byte]$SetSystemInfoCmd = 0x58
    [byte]$GetSystemInfoCmd = 0x59
    [byte]$DefaultLUN = 0x00
    [byte]$IPMBProtocolType = 0x01
    [byte]$8023LANMediumType = 0x04
    [byte]$MaxChannel = 0x0b
    [byte]$EncodingAscii = 0x00
    [byte]$MaxSysInfoDataSize = 19
}

Process { 
    foreach ($cn in $ComputerName) {
    write-progress "Connecting to WMI on $cn"

    #Reset Variables
    $oIPMI = $null
    $oRet = $null

    #Get IPMI Instance
        try {
        if ((gwmi -computername $cn "win32_computersystem").manufacturer -match "VMware" ) { write-warning "$cn`:This is a Virtual Machine. Skipping BMC check";continue }
        $oIPMI=Get-WmiObject -Namespace root\WMI -Class MICROSOFT_IPMI -Computername $cn -ErrorAction stop
        }
        catch {
        if ($PSItem.Exception.Message -match "Invalid Class") { write-warning "$cn`: No BMC Found. If this server is Windows 2003, ensure the Hardware Management feature is installed" }
        if ($PSItem.Exception.Message -match "RPC Server Unavailable") {write-warning "$cn`: Could not connect to WMI on this host. Please ensure it is a windows machine and check firewalls"}
        }

    #If for whatever reason an IPMI object is not returned, skip this system and move on
    if (!$oIPMI) {write-warning "$cn`: No IPMI Object Returned. Skipping...";continue}

    #Create Result Info
    $IPMIResult = [ordered]@{}
    $IPMIResult.ComputerName = $cn

    #Get the LAN Channel and IP address if found
    [byte[]]$RequestData=@(0)
    $oMethodParameter=$oIPMI.GetMethodParameters("RequestResponse")
    $oMethodParameter.Command=$GetChannelInfoCmd
    $oMethodParameter.Lun=$DefaultLUN
    $oMethodParameter.NetworkFunction=0x06
    $oMethodParameter.RequestData=$RequestData
    $oMethodParameter.RequestDataSize=$RequestData.length
    $oMethodParameter.ResponderAddress=$BMCResponderAddress
    # http://msdn.microsoft.com/en-us/library/windows/desktop/aa392344%28v=vs.85%29.aspx
    $RequestData=@(0)
    [Int16]$iLanChannel=0
    [bool]$bFoundLAN=$false
    for(;$iLanChannel -le $MaxChannel;$iLanChannel++){
        $RequestData=@($iLanChannel)
        $oMethodParameter.RequestData=$RequestData
        $oMethodParameter.RequestDataSize=$RequestData.length
        try {
        $oRet=$null
        $oRet=$oIPMI.PSBase.InvokeMethod("RequestResponse",$oMethodParameter,(New-Object System.Management.InvokeMethodOptions))
        }
        catch [Exception] {
        write-warning "$CN`: Error While attempting to find LAN Channels";return
        }
        #$oRet
        if($oRet.ResponseData[2] -eq $8023LANMediumType){
            $bFoundLAN=$true
            break;
        }
    }

    $oMethodParameter.Command=$GetLANInfoCmd
    $oMethodParameter.NetworkFunction=0x0c

    

    if($bFoundLAN){
        $RequestData=@($iLanChannel,3,0,0)
        $oMethodParameter.RequestData=$RequestData
        $oMethodParameter.RequestDataSize=$RequestData.length
        $oRet=$oIPMI.PSBase.InvokeMethod("RequestResponse",$oMethodParameter,(New-Object System.Management.InvokeMethodOptions))
        $IPMIResult.BMCIPAddress = (""+$oRet.ResponseData[2]+"."+$oRet.ResponseData[3]+"."+$oRet.ResponseData[4]+"."+ $oRet.ResponseData[5] )
        $RequestData=@($iLanChannel,6,0,0)
        $oMethodParameter.RequestData=$RequestData
        $oMethodParameter.RequestDataSize=$RequestData.length
        $oRet=$oIPMI.PSBase.InvokeMethod("RequestResponse",$oMethodParameter,(New-Object System.Management.InvokeMethodOptions))
        $IPMIResult.BMCSubnetMask = (""+$oRet.ResponseData[2]+"."+$oRet.ResponseData[3]+"."+$oRet.ResponseData[4]+"."+ $oRet.ResponseData[5] )
        $RequestData=@($iLanChannel,5,0,0)
        $oMethodParameter.RequestData=$RequestData
        $oMethodParameter.RequestDataSize=$RequestData.length
        $oRet=$oIPMI.PSBase.InvokeMethod("RequestResponse",$oMethodParameter,(New-Object System.Management.InvokeMethodOptions))
        # Format http://msdn.microsoft.com/en-us/library/dwhawy9k.aspx
        $IPMIResult.BMCMACAddress = ("{0:x2}:{1:x2}:{2:x2}:{3:x2}:{4:x2}:{5:x2}" -f $oRet.ResponseData[2], $oRet.ResponseData[3],$oRet.ResponseData[4], $oRet.ResponseData[5], $oRet.ResponseData[6], $oRet.ResponseData[7])
    } #If
    
    #Output the result
    new-object PSObject -Property $IPMIResult
} #Foreach
} #Process

} #Get-WMIBMCIPAddress

function Convert-HashTableToXML() {
<#
.SYNOPSIS
COnverts one or more Powershell hashtables into a simple XML format.
 
.DESCRIPTION
Creates simpler and more human-readable output for a hashtable than Export-CliXML or ConvertTo-XML.
This is useful for instance when storing attributes or configuration variables for output to other
program or storage in an AD CustomAttribute.
 
This command will create appropriate subnodes if you have nested hashtables.
 
.NOTES
Adapted from original script by Blindrood (https://gallery.technet.microsoft.com/scriptcenter/Export-Hashtable-to-xml-in-122fda31)
 
.PARAMETER InputObject
A Powershell hashtable that contains the name-value pairs you wish to convert to XML elements
 
.PARAMETER Root
Allows you to specify the root XML element definition.
 
.PARAMETER OutPath
Path to an output XML file, if desired. If not specified, outputs directly to the pipeline
 
.EXAMPLE
Create a Hashtable
 
PS C:\> $Configuration = @{
    'Definitions' = @{
        'ConnectionString' = 'sql=srv01;port=223'
        'MonitoringLevel' = 'MonitoringLevelValue'
    }
    'Conventions' = @{
        'MyConvention' = 'This is my convention'
        'Option' = 'Zip'
        'ServerType' = 'sql'
        'Actions' = @{
            'SpecificAction' = 'DoNothing'
            'DefaultAction' = 'Destroy it All'
        }
        'ExceptionAction' = 125
        'Period' = New-TimeSpan -Seconds 20
    }
    'ServiceAccount' = @{
        'UserName' = 'mydomain.com\thisisme'
        'Password' = '123o123'
    }
    'GroupConfiguration' = @{
        'AdminsGroup' = 'mydomain.com\thisisAdminsGroup'
        'UsersGroup' = 'mydomain.com\thisisUsersGroup'
    }
}
 
.EXAMPLE
Export the
$Configuration | Out-HashTableToXml -Root 'Configuration' -File $env:temp\test.xml
 
-----------------
Test.XML Contents
-----------------
 
<Configuration>
  <Conventions>
    <ExceptionAction>125</ExceptionAction>
    <ServerType>sql</ServerType>
    <Actions>
      <SpecificAction>DoNothing</SpecificAction>
      <DefaultAction>Destroy it All</DefaultAction>
    </Actions>
    <Period>00:00:20</Period>
    <Option>Zip</Option>
    <MyConvention>This is my convention</MyConvention>
  </Conventions>
  <GroupConfiguration>
    <UsersGroup>mydomain.com\thisisUsersGroup</UsersGroup>
    <AdminsGroup>mydomain.com\thisisAdminsGroup</AdminsGroup>
  </GroupConfiguration>
  <Definitions>
    <MonitoringLevel>MonitoringLevelValue</MonitoringLevel>
    <ConnectionString>sql=srv01;port=223</ConnectionString>
  </Definitions>
  <ServiceAccount>
    <Password>123o123</Password>
    <UserName>mydomain.com\thisisme</UserName>
  </ServiceAccount>
</Configuration>
 
 
#>


Param(
    [Parameter(ValueFromPipeline = $true, Position = 0)]
    [System.Collections.Hashtable]$InputObject,

    [ValidateScript({Test-Path $_ -IsValid})] 
    [System.String]$OutPath,

    [System.String]$Root="PSHashTable"
)

Begin{
    $ScriptBlock = {
        Param($Elem, $Root)
        if( $Elem.Value -is [System.Collections.Hashtable] ){
            $RootNode = $Root.AppendChild($Doc.CreateNode([System.Xml.XmlNodeType]::Element,$Elem.Key,$Null))
            $Elem.Value.GetEnumerator() | ForEach-Object {
                $Scriptblock.Invoke( @($_, $RootNode) )
            }
        }
        else{
            $Element = $Doc.CreateElement($Elem.Key)
            $Element.InnerText = if($Elem.Value -is [Array]) {
                $Elem.Value -join ','
            }
            else{
                $Elem.Value | Out-String
            }
            $Root.AppendChild($Element) | Out-Null    
        }
    }    
} #Begin

Process{
    $Doc = [xml]"<$($Root)></$($Root)>"
    $InputObject.GetEnumerator() | ForEach-Object {
        $scriptblock.Invoke( @($_, $doc.DocumentElement) )
    }
    
    #Output the formatted XML document if OutPath is specified, otherwise send to pipeline
    if ($OutPath) {$doc.save($OutPath)}
    else {$doc}
} #Process

} #Out-HashTableToXML

###########################################
# Function Get-WmiCustom([string]$computername,[string]$namespace,[string]$class,[int]$timeout=15)
# by Daniele Muscetta, MSFT
# originally published at http://www.muscetta.com/2009/05/27/get-wmicustom/
#
# works as a replacement for the Get-WmiObject cmdlet,
# but includes an extra parameter for specifying a client timeout.
#
###########################################
Function Get-WmiCustom([string]$class,[string]$computername = "localhost",[string]$namespace = "root\cimv2",[int]$timeout=15)
{
    $ConnectionOptions = new-object System.Management.ConnectionOptions
    $EnumerationOptions = new-object System.Management.EnumerationOptions 
    
    $timeoutseconds = new-timespan -seconds $timeout
    $EnumerationOptions.set_timeout($timeoutseconds) 
    
    $assembledpath = "\\" + $computername + "\" + $namespace
    #write-host $assembledpath -foregroundcolor yellow
    
    $Scope = new-object System.Management.ManagementScope $assembledpath, $ConnectionOptions
    $Scope.Connect() 
    
    $querystring = "SELECT * FROM " + $class
    #write-host $querystring
    
    $query = new-object System.Management.ObjectQuery $querystring
    $searcher = new-object System.Management.ManagementObjectSearcher
    $searcher.set_options($EnumerationOptions)
    $searcher.Query = $querystring
    $searcher.Scope = $Scope 
    
    
    
    return $searcher.get() 
}