DEV Community

mcc85sx
mcc85sx

Posted on

A Deep Dive: [PowerShell, XAML/WPF, Classes, Functions, and Networking]

Today, the world is full of a wide variety of programming choices.
Not everyone that tries to learn how to program, is successful in their endeavor.

Now, because it is tough to do right, AND, because it can be highly stressful to think about abstract solutions to real world problems…?

Having an example to work with, doesn't hurt.

Alt Text

When it comes to learning how it all works…?
Having an obsession with problem solving is a good first step.
That alone, won't be enough to build a tool of such calibur, however.

Second, is working on something that has a sense of practicality to it. That means, real world use case scenarios.

If the idea is (practical/beneficial)…?
Then, it is probably worth investing (time/resources) into creating it, or at the very least, understanding it.

Suffice to say, 1) drive and 2) inspiration go a long way if you really are taking a journey deeper into application development.

There are a number of other caveats that I wish I could have been told by somebody when I first started, however, that's not to say that I haven't learned a lot.

In this lesson, I’m going to talk about a range of things that are on the [intermediate-advanced] side... So if you're relatively new to programming or PowerShell... this may seem a bit daunting to follow.

The tool I will be discussing, is a network utility that I had to conceptualize for a tool to orchestrate DC Promo.

Now, I will discuss the perilous journey I had to take…
…in order to learn how to build it.


Foreword: I once had an opportunity to learn how to program many years ago when I was a kid, but I gravitated toward design work.

Whether it was designing websites or building levels for Quake, I had a future in graphic design in mind.

Alt Text

Alt Text

Alt Text

Alt Text

These are all taken from a map I made around the time that Doom 3 was in it's heyday, and when I was attending ITT Technical Institute for drafting and design, in 2006. I was given a preliminary course on programming in Visual Basic.Net, but wasn't very interested in it.

After a number of jobs that weren’t design related… I went back to school for MCSA/MCSE training. A number of years went by, being a (computer/network technician/administrator/engineer), in addition to having solved many other technical problems in some creative way…

Well, I began to revisit many of my print-based-graphics days where I made print documents with notepad and HTML tables, and then loaded up the web browser and then bam. Print that.

I found myself having a higher amount of interest in PowerShell year after year... because lets face it.

This PowerShell thing looked like it'd make my life a lot easier. I saw that I could theoretically do some serious (damage/development work) with it...

Reminded me a lot of Hyperterminal back in the day when I used it to connect to Cisco routers and switches in high school.

As it turns out...
Its 1) potential, 2) power, 3) ease of use, 4) compatibility, 5) scalability, and 6) usefulness… not to mention 7) security...?

Well, all of those things prompted me to spend the last 2 years researching and developing uncommon methods of using PowerShell in ways that no sane man has done yet.

Alt Text

Cause. All of my technical abilities with a computer, or a network…? They all seemed to be much more manageable via this whole “PowerShell” phenomenon.

I remember many years ago... an instructor I once had told me how powerful this “Monad/PowerShell” would be… I only wish I had listened to him more carefully. I also remember a kid that talked about Slackware back in 2001... so I had some interest in the programming scene...

Intrigued enough to begin reviewing and studying like nobody's business...? Well, I ventured into the darkest chapters of the online PowerShell sacred dungeon/library archives... where some of the lightest and darkest mages of all time, have left many pages worth of spells for both good and evil purposes...

Now, to combat dark mage wizardry…?
You need a proper master to train you.

Legend has it, that if you have the heart and mind of a worthy warrior... then you'll be able to pull the Excalibur from the stone. That's where Merlin comes into play…

That's right, Merlin, the wizard supreme.
Keep in mind, Merlin could be a code name.

I like to look at this PowerShell, as if I could be King Arthur himself…
…finally removing the Excalibur from the enchanted stone of Camelot…

Merlin, the wizard supreme… watching from a distance…
…keeping a careful, AND watchful eye over this process…
…of a boy named Arthur who grew up as one among the commonwealth…
…to the man who pulled the sword from the enchanted stone…
…because it was his fate to be the best king that ever lived.

Now, that probably sounds wicked intense.
Way more intense than you were expecting, probably.

While there probably are an infinite number of ways that you can use this PowerShell… you don’t want to go recklessly adding yourself to the list of people that Merlin’s gotta keep a watchful eye over, for safety reasons…

Cause. Whether you’re comparing PowerShell to the Excalibur, or not…? (Microsoft/Merlin) knew how much power (PowerShell/Excalibur) was capable of, since its inception.

Seriously.
Why do you think they made it…?
It’s because they needed an extremely powerful AND versatile tool that didn’t need to be compiled, in order to run.
You might get just-in-time compilation, but that’s it…
…you highlight the text, press F8, and bam.
There it is.

Regardless, because of how much it is capable of… well, there is a lot of responsibility involved in the handling of this PowerShell.
So, if you’re going to learn how to use it…?
Then, make sure you use it responsibly…
That's all I'm gonna say about that.

End Foreword

So, here is a listing of all the "code-behind" components of the GUI.
This doesn't include the "code-front" XAML/Window object...

Function Get-FENetwork ..
Class _VendorList ..
Class _NbtReferenceObject ..
Class _NbtReference ..
Class _NbtHostObject ..
Class _NbtTable ..
Class _NbtStat ..
Class _V4PingObject ..
Class _V4PingSweep ..
Class _V4Network ..
Class _V6Network ..
Class _NetInterface ..
Class _ArpHostObject ..
Class _ArpTable ..
Class _ArpStat ..
Class _NetStatAddress ..
Class _NetStatObject ..
Class _NetStat ..
Class _Controller ..
Class _DGList ..
Class _FENetwork ..
Enter fullscreen mode Exit fullscreen mode

Granted, I probably don't need this many classes to do what I'm doing, but I like to keep groups of variables together and a strange side effect of this, is that the process remains quite optimized.

The main function/namespace is Get-FENetwork.
I previously had these classes outside of this function... however, that is a long list of objects/files to maintain and keep track of, when all of them correspond to networking functions.

When classes are embedded within a function, it provides a scope for those classes to exist within... so, if the namespaces that a class or type needs, aren't loaded FIRST- then you'll run into problems.


What is a function...?

A function is essentially an executable, whether it is a codeblock or a program. Variables are technically functions as well, but... I'm not saying that they definitely are. Functions/variables are ALSO used in mathematics to determine property values, as well as in theorems or algorithms.

What is a class, anyway?

A class is a special function. One that has an extra added bonus, of being able to carry out additional functions within itself. Functions can also do this too, but to be REFERENCABLE...? It is better to use classes for this.

Anyway, classes are essentially fancy functions.

Like a guy in a limo just driving right up next to you in your limo, in the most nonchalant manner, rolling his window down...
...asking you if you have any "Grey Poupon"...
...that type of fancy.

In C++ I believe classes are called structs, I'm not positive.

Classes have properties, that functions typically do not have. Classes can be embedded within functions and vice versa, however the scope from which that embedded class or function can be used, remains within that container function/class.

If that is confusing, don't worry. It is. The main trick is, learning how to deal with said confusion in an orchestrated way, AND, forcing things to work correctly. In order to do this, you'll be tempted to experiment and figure out where you can do certain things, and where you can't.


The name of the first class is "_VendorList".

It is good practice to use names that make sense and are revealing about what that object is. You wouldn't call your buddy "Barbie Doll" if his name is Jim... right?

Alright then.
Make things easier on yourself.
Name your functions/classes so that they are relevant to the output.

I haven't been an application developer over the last 20 years, but I have used a number of them and have had to do light programming/config file editing over the years, and I have seen many programmers name a function something that is incredibly cryptic and hard for someone that isn't the software developer to repair.

Sometimes that process is referred to as obfuscation.
Other times, it is just questionable code that someone slapped together.
The difference between the two can be incredibly difficult to discern.

Anyway...
The class "_Vendorlist" seeks to pull a list of (hex = vendor) codes from the same codebase as nmap. At one point I had edited the data so that the vendorlist was 400kb smaller than the original file, but that was a pain in the neck to do.

Class VendorList is going to have several properties, but the only property that matters in an external case, is the GetVendor() method.

So if I hand a MacAddress to the method...

$MacAddress = "00-50-56-C0-00-01"
$This.GetVendor($MacAddress)
Enter fullscreen mode Exit fullscreen mode

...then the output will be "VMware".

Here's an examination of the class "_VendorList"

Class _VendorList
{
    Hidden [Object]    $File
    [String[]]          $Hex
    [String[]]        $Names
    [String[]]         $Tags
    [Hashtable]          $ID
    [Hashtable]       $VenID

    _VendorList([String]$Path)
    {
        Switch ([Int32]($Path -Match "(http|https)"))
        {
            0
            {
                If ( ! ( Test-Path -Path $Path ) )
                {
                    Throw "Invalid Path"
                }

                $This.File = (Get-Content -Path $Path) -join "`n"
            }

            1
            { 
                [Net.ServicePointManager]::SecurityProtocol = 3072

                $This.File = Invoke-RestMethod -URI $Path

                If ( ! $This.File )
                {
                    Throw "Invalid URL"
                }
            }
        }

        $This.Hex            = $This.File -Replace "(\t){1}.*","" -Split "`n"
        $This.Names          = $This.File -Replace "([A-F0-9]){6}\t","" -Split "`n"
        $This.Tags           = $This.Names | Sort-Object
        $This.ID             = @{ }

        ForEach ( $I in 0..( $This.Tags.Count - 1 ) )
        {
            If ( ! $This.ID[$This.Tags[$I]] )
            {
                $This.ID.Add($This.Tags[$I],$I)
            }
        }

        $This.VenID          = @{ }
        ForEach ( $I in 0..( $This.Hex.Count - 1 ) )
        {
            $This.VenID.Add($This.Hex[$I],$This.Names[$I])
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, this class should immediately cause someone to ask questions about what it is doing, especially when they see the first block of variables.

These variables...

Hidden [Object]    $File
[String[]]          $Hex
[String[]]        $Names
[String[]]         $Tags
[Hashtable]          $ID
[Hashtable]       $VenID
Enter fullscreen mode Exit fullscreen mode

These here, are properties.

The first property is "File"

Above, the property/variable is accessed via $File

Once the _VendorList(){} method is entered... then the variable $File, will be accessible via $This.File.

That one reason alone, is why classes are far more flexible than naked functions. It's as if you are setting up strings that go through wormholes and back.

At any rate, lets not get lost in charades that involve manipulation of the time-space continuum.
Eyes on the prize.

When I first review a block of code, I look at the variables/properties so that I can keep those values or placeholders in mind as I review the sample.

In this case, $File is an OBJECT placeholder for the string...
So, we're likely to load a UNC path or a file system path.

Switch ([Int32]($Path -Match "(http|https)"))
{
    0
    {
        If ( ! ( Test-Path -Path $Path ) )
        {
            Throw "Invalid Path"
        }

        $This.File = (Get-Content -Path $Path) -join "`n"
    }

    1
    { 
        [Net.ServicePointManager]::SecurityProtocol = 3072

        $This.File = Invoke-RestMethod -URI $Path

        If ( ! $This.File )
        {
            Throw "Invalid URL"
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This is a switch that I will likely rewrite.
It is using regex to test a pattern on the input string, and while it does work... it leaves the possibility of problems happening.

Regardless, the input string is being checked for either "http" or "https".
With regex, I probably could've used "(http[s*])"... but the pipe symbol in the middle of "(http|https)" allows for either/or.

If the string does not match, then the file system will test the path of the object.
If it is not there, it will throw the error.
If it is, then the property, $This.File is then provided the content of that file.

If the string does match, then the shell will set the download security protocol to TLS 1.2, and then commence with an Invoke-RestMethod command...
...whereby pulling content from a URL.

$This.Hex            = $This.File -Replace "(\t){1}.*","" -Split "`n"
$This.Names          = $This.File -Replace "([A-F0-9]){6}\t","" -Split "`n"
$This.Tags           = $This.Names | Sort-Object
$This.ID             = @{ }
Enter fullscreen mode Exit fullscreen mode

This block here is making adjustments to the content using regex.
It is best to manipulate content when it is all one contiguous string, for performance reasons.

When the content is split into lines, then the regex function has to work harder and more often, resulting in more time being needed to accomplish the same goal.

$This.Hex is pulling all of the Hex codes from the sample input.
$This.Names is pulling all of the vendor names from the sample input.
$This.Tags is taking all of the names and then sorting them alphabetically.
$This.ID is setting up a hashtable so that the components of each string/line can be readded.

ForEach ( $I in 0..( $This.Tags.Count - 1 ) )
{
    If ( ! $This.ID[$This.Tags[$I]] )
    {
        $This.ID.Add($This.Tags[$I],$I)
    }
}
Enter fullscreen mode Exit fullscreen mode

Hashtables are great for storing data in a way that allows for immediate reaccessiblity.

I prefer to use arrays to collect objects, however, sometimes the hashtable collection/recollection process is faster.

Hashtable vs. Array analogy...

Suppose you're in charge of delivering a bunch of packages to each and every floor on a particular building... if you can take all of the packages with you, then you could drop them all off when going from top to bottom... that's what an array does.

With a hashtable, it's different. You're not going from top to bottom based on numerical index, you're probably doing so in alphabetical order. You just say the name of the person that needs their package, and then they show up at the lobby.

With an array, it has to destroy and recreate the entire array in order to add an item to it. Hashtables don't work that way. Which is why learning when and where to use each tool is vital.

End analogy

$This.ID is holding a numerical value for each unique entry in the list of vendor names, for rereferencing.

$This.VenID          = @{ }
ForEach ( $I in 0..( $This.Hex.Count - 1 ) )
{
    $This.VenID.Add($This.Hex[$I],$This.Names[$I])
}
Enter fullscreen mode Exit fullscreen mode

Now, this part here says to me that some of the logic isn't being used, but that's ok.

Sometimes I will leave logic that accomplishes some other goal, within the code that produces the correct result.

In this case, the sorting process for $This.ID isn't being used.
It isn't impacting the performance of the class by much to leave it in there.

Alt Text

Now, all of that code thus far, allows the vendor name to be discoverable from the Mac Address.
So, in the case of wanting to find devices on a given network that aren't supposed to be there...?
It helps to know if there's a hidden "Dell" in your house or business...

Which is why this part of the Get-FENetwork function is critical.

Now I'll talk about NBT stat, which was necessary for building a new version of DC Promo...

Not going to get into the DC Promo tool quite yet... but, NBT stat parsings required a few classes.

Class _NbtReferenceObject
{
    [String]      $ID
    [String]    $Type
    [String] $Service

    _NbtReferenceObject([String]$In)
    {
        $This.ID, $This.Type, $This.Service = $In -Split "/"
        $This.ID = "<$($This.ID)>"
    }
}
Enter fullscreen mode Exit fullscreen mode

This one is rather quick and concise.

Every "registered" object that shows up with the nbtstat -N utility is parsed and objectified by matching values here.

Class _NbtReference
{
    [String[]] $String = (("00/{0}/Workstation {4};01/{0}/Messenger {6};01/{1}/Master Browser;03/{0}/Messenger {6};" + 
    "06/{0}/RAS Server {6};1F/{0}/NetDDE {6};20/{0}/File Server {6};21/{0}/RAS Client {6};22/{0}/{2} Interchange(MSMail C" + 
    "onnector);23/{0}/{2} Exchange Store;24/{0}/{2} Directory;30/{0}/{4} Server;31/{0}/{4} Client;43/{0}/{3} Control;44/{" + 
    "0}/SMS Administrators Remote Control Tool {6};45/{0}/{3} Chat;46/{0}/{3} Transfer;4C/{0}/DEC TCPIP SVC on Windows NT" +
    ";42/{0}/mccaffee anti-virus;52/{0}/DEC TCPIP SVC on Windows NT;87/{0}/{2} MTA;6A/{0}/{2} IMC;BE/{0}/{5} Agent;BF/{0}" + 
    "/{5} Application;03/{0}/Messenger {6};00/{1}/{7} Name;1B/{0}/{7} Master Browser;1C/{1}/{7} Controller;1D/{0}/Master " + 
    "Browser;1E/{1}/Browser {6} Elections;2B/{0}/Lotus Notes Server;2F/{1}/Lotus Notes ;33/{1}/Lotus Notes ;20/{1}/DCA Ir" + 
    "maLan Gateway Server;01/{1}/MS NetBIOS Browse Service") -f "UNIQUE","GROUP","Microsoft Exchange","SMS Clients Remote",
    "Modem Sharing","Network Monitor","Service","Domain").Split(";")
    [Object[]] $Output

    _NbtReference()
    {
        $This.Output = @( )
        $This.String | % { 

            $This.Output += [_NbtReferenceObject]::New($_)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This is essentially an object that charts out what the items found on an NBT map have for a service.

Each item found in $This.String, gets parsed into an object and $This.Output collects each iteration.

Heres how to get that output...

PS:\> [_NBTReference]::New().Output

ID   Type   Service                                         
--   ----   -------                                         
<00> UNIQUE Workstation Modem Sharing                       
<01> UNIQUE Messenger Service                               
<01> GROUP  Master Browser                                  
<03> UNIQUE Messenger Service                               
<06> UNIQUE RAS Server Service                              
<1F> UNIQUE NetDDE Service                                  
<20> UNIQUE File Server Service                             
<21> UNIQUE RAS Client Service                              
<22> UNIQUE Microsoft Exchange Interchange(MSMail Connector)
<23> UNIQUE Microsoft Exchange Exchange Store               
<24> UNIQUE Microsoft Exchange Directory                    
<30> UNIQUE Modem Sharing Server                            
<31> UNIQUE Modem Sharing Client                            
<43> UNIQUE SMS Clients Remote Control                      
<44> UNIQUE SMS Administrators Remote Control Tool Service  
<45> UNIQUE SMS Clients Remote Chat                         
<46> UNIQUE SMS Clients Remote Transfer                     
<4C> UNIQUE DEC TCPIP SVC on Windows NT                     
<42> UNIQUE mccaffee anti-virus                             
<52> UNIQUE DEC TCPIP SVC on Windows NT                     
<87> UNIQUE Microsoft Exchange MTA                          
<6A> UNIQUE Microsoft Exchange IMC                          
<BE> UNIQUE Network Monitor Agent                           
<BF> UNIQUE Network Monitor Application                     
<03> UNIQUE Messenger Service                               
<00> GROUP  Domain Name                                     
<1B> UNIQUE Domain Master Browser                           
<1C> GROUP  Domain Controller                               
<1D> UNIQUE Master Browser                                  
<1E> GROUP  Browser Service Elections                       
<2B> UNIQUE Lotus Notes Server                              
<2F> GROUP  Lotus Notes                                     
<33> GROUP  Lotus Notes                                     
<20> GROUP  DCA IrmaLan Gateway Server                      
<01> GROUP  MS NetBIOS Browse Service 
Enter fullscreen mode Exit fullscreen mode

Now compare the properties in the above class to the properties in the one below

Class _NbtHostObject
{
    Hidden [String[]]  $Line
    [String]           $Name
    [String]             $ID
    [String]           $Type
    [String]        $Service

    _NbtHostObject([String]$Line)
    {
        $This.Line    = $Line.Split(" ") | ? Length -gt 0
        $This.Name    = $This.Line[0]
        $This.ID      = $This.Line[1]
        $This.Type    = $This.Line[2]
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, the object looks very similar to [_NbtReferenceObject], but it has an additional property.

That is because this the first (2) NBT classes are only meant to be used as a reference.

That means that any actual raw data has yet to be processed.

The first (2) classes are merely guides that keep the input/output clean, and accessible.

Each input string $Line is fed into this function, where $This.Line collects it while also splitting it.

This line alone does all of the work, the other (3) lines just assign a slice of that array.

Class _NbtTable
{
    [String]      $Name
    [String] $IpAddress
    [Object]     $Hosts

    _NbtTable([String]$Name)
    {
        $This.Name = $Name
        $This.Hosts = @( )
    }

    NodeIp([String]$Node)
    {
        $This.IpAddress = [Regex]::Matches($Node,"(\d+\.){3}(\d+)").Value
    }

    AddHost([String]$Line)
    {
        $This.Hosts += [_NbtHostObject]::New($Line)
    }
}
Enter fullscreen mode Exit fullscreen mode

This, is meant to collect pieces of the NBT table. It is capturing raw output.

This class is making use of additional methods, to which they are called externally.

So these methods "NodeIp", and "AddHost", they are dependent on input from an outside scope.

The method "NodeIp" is collecting an IP Address from the brackets using a [Regex]::Matches($Content,$Pattern).Value trick.

Class _NbtStat
{
    Hidden [Object] $Alias
    Hidden [Object] $Table
    Hidden [Object] $Section
    [Object] $Output

    _NbtStat([Object[]]$Interface)
    {
        $This.Alias   = $Interface.Alias | % { "{0}:" -f $_ }
        $This.Table   = nbtstat -N
        $This.Section = @{ }
        $X            = -1

        ForEach ( $Line in $This.Table )
        {
            If ( $Line -in $This.Alias )
            {
                $X ++
                $This.Section.Add($X,[_NbtTable]::New($Line))
            }

            ElseIf ( $Line -match "Node IpAddress" )
            {
                $This.Section[$X].NodeIp($Line)
            }

            ElseIf ( $Line -match "Registered" )
            {
                $This.Section[$X].AddHost($Line)
            }
        }

        $This.Output = $This.Section | % GetEnumerator | Sort-Object Name | % Value
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, here's where the last (4) classes that I talked about start to converge.

Because there are typically multiple adapters in any given device, it is best to handle them all as a group of (interface/adapter)s, rather than each individual (interface/adapter)...

So, whether it is NO adapters, ONE adapter, or MULTIPLE adapters, then this will handle all of those cases.

I began to use a theme in collecting raw input from these legacy console commands like arp and nbtstat.

Hidden [Object]   $Alias
Hidden [Object]   $Table
Hidden [Object] $Section
[Object]         $Output
Enter fullscreen mode Exit fullscreen mode

The first 3 properties here are hidden, all we really want is the output.

But, that output needs to be generated based on these hidden values.

In PowerShell, hidden items can still be accessed if the hidden properties hasn't been filtered out yet.

Hidden items just simply aren't drawn to the screen.

Also, hiding input/output is an artform in and of itself... but I digress.

$Alias is going to collect the name of the interface.
$Table is going to collect the output of the console application, in this case "nbtstat -N"
$Section is going to parse out each section of the table, so that it can be sorted and added to the correct (interface/adapter).
$Output is the result of these things being mixed together like a cocktail of correct information.

Lets talk about this line specifically...
"_NbtStat([Object[]]$Interface)"

This is where the class _NbtStat and all of the prior NBT based classes are initialized.

[Object[]]$Interface is an array of interface objects.

$This.Alias   = $Interface.Alias | % { "{0}:" -f $_ }
$This.Table   = nbtstat -N
$This.Section = @{ }
$X            = -1

ForEach ( $Line in $This.Table )
{
    If ( $Line -in $This.Alias )
    {
        $X ++
        $This.Section.Add($X,[_NbtTable]::New($Line))
    }

    ElseIf ( $Line -match "Node IpAddress" )
    {
        $This.Section[$X].NodeIp($Line)
    }

    ElseIf ( $Line -match "Registered" )
    {
        $This.Section[$X].AddHost($Line)
    }
}

$This.Output = $This.Section | % GetEnumerator | Sort-Object Name | % Value
Enter fullscreen mode Exit fullscreen mode

Here, the Interface object has a property named "Alias".

For each of those objects, a colon is added to the string to match the nbtstat content, and pair it with its correct counterpart.

If a match is made, then a new instantiation of class _NbtTable with that line as input is created.

Each line afterward is processed and checked for (1) of those (3) conditions.

If a condition is matched, then the line is handed off to the corresponding method in _NbtTable.

If the line matches an adapter alias, then guess what...? The current table is complete, and a new one is opened up.

At the tail end of each section, the name/value pairs are enumerated, sorted by name, an then the value is selected...
and that process is saved to the output property.

The items in the following DataGrid control box, they were created with the above processes/classes.

Alt Text

In the XAML/WPF section, I will discuss how to tie the classes/variables to XAML controls via WPF binding.

Class _V4PingObject
{
    Hidden [Object]   $Reply
    [UInt32]          $Index
    [String]         $Status
    [String]      $IPAddress
    [String]       $Hostname

    _V4PingObject([UInt32]$Index,[String]$Address,[Object]$Reply)
    {
        $This.Reply          = $Reply.Result
        $This.Index          = $Index
        $This.Status         = @("-","+")[[Int32]($Reply.Result.Status -match "Success")]
        $This.IPAddress      = $Address
        $This.Hostname       = Switch ($This.Status)
        {
            "+"
            {
                Resolve-DNSName $This.IPAddress | % NameHost
            }

            Default
            {
                "-"
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In Class _V4PingObject, this object helps to sort through an asynchronous pinging of all hosts on a subnet.

By itself, there's not much to say.

If the reply result status matches success, then the command "Resolve-DNSName" is issued.

Resolve-DNSName is a lengthy command when a DNS zone isn't properly configured, and so far it is what takes the most time with the utility so far.

As far as DNS is concerned, I will not get into DNS management in this lesson... though that is definitely important in networking.

Knowing whether A/PTR records exist, and how they relate to obtaining hostnames...?

That is all very relevant to the task at hand.

Class _V4PingSweep
{
    [String]         $HostRange
    [String[]]       $IPAddress
    Hidden [Hashtable] $Process
    [Object] $Buffer         = @( 97..119 + 97..105 | % { "0x{0:X}" -f $_ } )
    [Object] $Options
    [Object] $Output
    [Object] $Result

    _V4PingSweep([String]$HostRange)
    {
        $This.HostRange = $HostRange
        $Item           = $HostRange -Split "/"

        $Table          = @{ }
        $This.Process   = @{ }

        ForEach ( $X in 0..3 )
        {
            $Table.Add( $X, (Invoke-Expression $Item[$X]) )
        }

        $X = 0

        ForEach ( $0 in $Table[0] )
        {
            ForEach ( $1 in $Table[1] )
            {
                ForEach ( $2 in $Table[2] ) 
                {
                    ForEach ( $3 in $Table[3] )
                    {
                        $This.Process.Add($X++,"$0.$1.$2.$3")
                    }
                }
            }
        }

        $This.IPAddress      = $This.Process | % GetEnumerator | Sort-Object Name | % Value
        $This._Refresh()
    }

    _Refresh()
    {
        $This.Process        = @{ }

        ForEach ( $X in 0..( $This.IPAddress.Count - 1 ) )
        {
            $IP              = $This.IPAddress[$X]

            $This.Options    = [System.Net.NetworkInformation.PingOptions]::new()
            $This.Process.Add($X,[System.Net.NetworkInformation.Ping]::new().SendPingAsync($IP,100,$This.Buffer,$This.Options))
        }

        $This.Output         = @( )

        ForEach ( $X in 0..( $This.IPAddress.Count - 1 ) ) 
        {
            $IP              = $This.IPAddress[$X] 
            $This.Output    += [_V4PingObject]::New($X,$IP,$This.Process[$X])
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This one here, is a bit of a doozy.

In this specific case, the shaolin master that I studied from in order to write this, is Boe Prox.

While he may not be an actual shaolin master..?
Thats beside the point...
He practically is one based on his plethora of knowledge in things PowerShell related.

Mr. Prox wrote an article featuring the differences between asynchronous pinging and then pinging in other ways. I don't have the specific link.

But, I jumped into tweaking what his function did until it suited the conditions that could scan an IPv4 network of any size...

Because I thought perhaps threading might be a great way to ping that many hosts...

Boe Prox also developed POSH-RSJobs, which can be found here... https://github.com/proxb/PoshRSJob

Ive had to take a step back from the idea of spinning up threads willy-nilly... because the possibilities begin to ramp up drastically when mulling over what you could do...

Anyway, depending on its subnet mask, gateway, etc... well, this _V4PingSweep object will take a single string named "HostRange"...
...and it takes that string, and expands it.

Now... you could have your standard issue 250ish hosts on a network, and the function will work fast.

But, you could also have a network with 16777214 hosts on it, if not more via VLSM...

I probably don't have to say it, but... if you have a network of that size... then you're probably not going to want to wait for the function I wrote to ping each individual host on that network, in order to locate an active directory domain controller that you can use to promote a new domain, domain controller or forest...

But, even if you do...?
Then the way I wrote it, should take the same amount of time...
In other words, it should only take a few seconds if DNS is properly configured on your network.

In order to really dive deeply into this class however, I have to discuss this much longer one below.

Class _V4Network
{
    [String]            $IPAddress
    [String]                $Class
    [Int32]                $Prefix
    [String]              $Netmask
    Hidden [Object]         $Route
    [String]              $Network
    [String]              $Gateway
    [String[]]             $Subnet
    [String]            $Broadcast
    [String]            $HostRange

    [String] GetNetmask([Int32]$CIDR)
    {
        $Switch         = 0

        Return @( ForEach ( $I in 0..3 )
        {
            If ( $CIDR -in @{ 0 = 1..7; 1 = 8..15; 2 = 16..23; 3 = 24..30 }[$I] )
            {
                $Switch = 1
                @(0,128,192,224,240,248,252,254,255)[$CIDR % 8]
            }

            Else
            {
                @(255,0)[$Switch]
            }
        }) -join "."
    }

    IPCheck()
    {
        $Item = [IPAddress]$This.IPAddress | % GetAddressBytes

        If ( $Item[0] -in @(0,127;224..255) )
        {
            Throw "Invalid Address Detected"
        }

        If ( ( $Item[0..1] -join '.' ) -eq "169.254" )
        {
            Throw "Automatic Private IP Address Detected"
        }
    }

    GetHostRange()
    {
        $Item           = [IPAddress]$This.IPAddress | % GetAddressBytes 
        $Mask           = [IPAddress]$This.Netmask   | % GetAddressBytes 
        $This.HostRange = @( ForEach ( $I in 0..3 )
        {
            $Step = 256 - $Mask[$I]

            Switch ( $Step )
            {
                1 
                { 
                    $Item[$I] 
                } 

                256 
                { 
                    "0..255" 
                } 

                Default 
                {
                    $Slot = 256 / $Step

                    ForEach ( $X in 0..( ( 256 / $Slot ) - 1 ) )
                    {
                        $IRange = ( $X * $Slot ) | % { $_..( $_ + $Slot - 1 ) }

                        If ( $Item[$I] -in $IRange )
                        {
                            "{0}..{1}" -f $IRange[0,-1]
                        }
                    }
                }
            }
        }) -join '/'
    }

    _V4Network([Object]$Address)
    {
        If ( ! $Address )
        {
            Throw "Address Empty"
        }

        $This.IPAddress = $Address.IPAddress
        $This.IPCheck()
        $This.Class     = @('N/A';@('A')*126;'Local';@('B')*64;@('C')*32;@('MC')*16;@('R')*15;'BC')[[Int32]$This.IPAddress.Split(".")[0]]
        $This.Prefix    = $Address.PrefixLength
        $This.Netmask   = $This.GetNetMask($This.Prefix)
        $This.Route     = Get-NetRoute -AddressFamily IPV4 | ? InterfaceIndex -eq $Address.InterfaceIndex
        $This.Network   = $This.Route | ? { ($_.DestinationPrefix -Split "/")[1] -match $This.Prefix } | % { ($_.DestinationPrefix -Split "/")[0] }
        $This.Gateway   = $This.Route | ? NextHop -ne 0.0.0.0 | % NextHop
        $This.Subnet    = $This.Route | ? DestinationPrefix -notin 255.255.255.255/32,224.0.0.0/4,0.0.0.0/0 | % DestinationPrefix | Sort-Object
        $This.Broadcast = ( $This.Subnet | % { ( $_ -Split "/" )[0] } )[-1]
        $This.GetHostRange()
    }

    [Object[]] ScanV4()
    {
        Return @( [_V4PingSweep]::New($This.HostRange).Output | ? Status -eq + )
    }
}
Enter fullscreen mode Exit fullscreen mode

What you're seeing here, is a chaotic consortium of considerably choreographed characters, class for short.

This class _V4Network... is massive.
But, it does a lot.

Any interface that has an IPV4 address is going to be collected, and then sent in here.

Sometimes an interface will have multiple addresses, and we want each address to have it's network mapped out correctly.

The address object is obtained and if it is empty/null, then the object will be thrown. Like Thor's hammer. Or, Captain America's shield.

The output will wind up looking like this...

IPAddress : 192.168.52.1
Class     : C
Prefix    : 24
Netmask   : 255.255.255.0
Network   : 192.168.52.0
Gateway   :
Subnet    : {192.168.52.0/24, 192.168.52.1/32, 192.168.52.255/32}
Broadcast : 192.168.52.255
HostRange : 192/168/52/0..255
Enter fullscreen mode Exit fullscreen mode

Here's a picture of what that looks like in a WPF datagrid.

Alt Text

The left side is the older, control.label-based approach, and the amount of code that the datagrid approach replaces, is staggering actually.

Knowing that all of these tables can be powered by PowerShell, and classes, that type of stuff... while also looking brilliant and functioning rather efficiently...?

I thought that it was worthy of figuring out.
Damien Van Robaeys from http://www.systanddeploy.com/, made a video on YouTube that covers this exact process that I'm sharing.

Maybe not the networking components, but the XAML based datagrid stuff with variables and such? That he did do.

Damien had a number of inspirational projects that I stumbled into while researching ways to automate the Microsoft Deployment Toolkit like a boss.

I saw a way to sew several ideas together, and this lesson here is something I conceptualized then.

My research into CSS Grid at or about that time, allowed me to see a way to apply my HTML/CSS skills to create XAML content. CSS grid doesn't hold a candle to XAML based grids. Nope. Thats why I like XAML a lot.

Perhaps JavaScript could be controlled by PowerShell or C#... Blazor is something that sorta does that...?

I intend to get back into full development with Blazor at some point.

At any rate, when comparing the left side to the right side in the image above... which one looks better to you?

I'm not likely to speak for others, but my opinion tells me that the right side looks slightly better.

That is a datagrid, controlled via the classes that I talk about above. In PowerShell. Not C#, no HTML, no CSS, no JavaScript, no image editing, this is a GUI that's ready to roll, with your standard issue installation of Windows 10.

Anyway, getting a datagrid to display a table of (name, value) pairs, you can get snagged pretty quickly. That's why you want to enumerate the values, get each name and value, and then that let that datagrid do it's magic.

That's what the class _DGList is for...

Class _DGList
{
    [String] $Name
    [Object] $Value
    _DGList([String]$Name,[Object]$Value)
    {
        $This.Name  = $Name
        $This.Value = $Value
    }
}
Enter fullscreen mode Exit fullscreen mode

I skipped ahead slightly, but this class was needed to "splat" the class into an object that the datagrid/table in the graphic above can display.

You can use the column headers when there are multiple objects, but when there is a single object, having a class like this to pipe the input/output into the datagrid.itemssource is helpful.

Class _V6Network
{
    [String] $IPAddress
    [Int32]  $Prefix
    [String] $Link

    _V6Network([Object]$Address)
    {
        $This.IPAddress = $Address.IPAddress
        $This.Prefix    = $Address.PrefixLength
    }
}
Enter fullscreen mode Exit fullscreen mode

This class will be getting additional attention, but its worth noting that IPV6 does not need as much micromanagement as IPV4.

Right now I'm about to cover the whole "_NetInterface" business, where a lot of the above classes get thrown together into a single control.

Class _NetInterface
{
    Hidden [Object] $Interface
    [String] $Hostname
    [String] $Alias
    [Int32]  $Index
    [String] $Description
    [String] $Status
    [String] $MacAddress
    [String] $Vendor
    [Object] $IPv4
    [Object] $IPv6
    [Object] $Nbt
    [Object] $Arp

    _NetInterface([Object]$Interface)
    {
        $This.Interface   = $Interface
        $This.HostName    = $Interface.ComputerName
        $This.Alias       = $Interface.InterfaceAlias
        $This.Index       = $Interface.InterfaceIndex
        $This.Description = $Interface.InterfaceDescription
        $This.Status      = $Interface.NetAdapter.Status
        $This.MacAddress  = $Interface.NetAdapter.LinkLayerAddress

        $This.IPV4        = @( )

        ForEach ( $Address in $Interface.IPV4Address ) 
        { 
            $This.IPV4   += [_V4Network]::New($Address)
        }

        $This.IPV4        = $This.IPV4 | Select-Object -Unique

        $This.IPV6        = @( )

        ForEach ( $Address in $Interface.IPV6Address)
        {
            $This.IPV6   += [_V6Network]::New($Address)
        }

        ForEach ( $Address in $Interface.IPV6LinkLocalAddress )
        {
            $This.IPV6   += [_V6Network]::New($Address)
        }

        ForEach ( $Address in $Interface.IPV6TemporaryAddress )
        {
            $This.IPV6   += [_V6Network]::New($Address)
        }

        $This.IPV6        = $This.IPV6 | Select-Object -Unique
    }

    GetVendor([Object]$Vendor)
    {
        $This.Vendor = $Vendor.VenID[ ( $This.MacAddress -Replace "(-|:)","" | % Substring 0 6 ) ]
    }

    Load([Object]$Nbt,[Object]$Arp)
    {
        $This.Nbt = $Nbt
        $This.Arp = $Arp
    }
}
Enter fullscreen mode Exit fullscreen mode

This is an instantiation of a class that allows us to pool together the variables and properties in the above classes.

The method GetVendor($Vendor) is fed the vendorlist in order to set the vendor tag.

The process in which it does this allows for the list to be prepared once, and then each adapter found in any object that calls the VendorList...
...to immediately return the correct vendor.

The method Load($Nbt,$Arp) is essentially putting the correct adapter/interface ARP and NBT pools into the interface, already parsed and objectified.

Class _ArpHostObject # Used to identify ARP network hosts
{
    Hidden [String]    $Line
    [String]       $Hostname
    [String]      $IpAddress
    [String]     $MacAddress
    [String]         $Vendor
    [String]           $Type

    _ArpHostObject([String]$Line)
    {
        $This.Line       = $Line
        $This.IpAddress  = $This.Line.Substring(2,22).Replace(" ","")
        $This.MacAddress = $This.Line.Substring(24,17)
        $This.Type       = $This.Line.Substring(46)
    }

    GetVendor([Object]$Vendor)
    {
        $This.Vendor     = $Vendor.VenID[ ( $This.MacAddress -Replace "(-|:)","" | % Substring 0 6 ) ]
    }
}
Enter fullscreen mode Exit fullscreen mode

This simply parses the arp table content it is fed into a host object.

You can see that it is the same method GetVendor as the prior class, but it is within a different object.

Class _ArpTable
{
    [String]      $Name
    [String] $IpAddress
    [Object]     $Hosts

    _ArpTable([String]$Line)
    {
        $This.Name      = $Line.Split(" ")[-1]
        $This.IPAddress = $Line.Replace("Interface: ","").Split(" ")[0]
        $This.Hosts     = @( )
    }
}
Enter fullscreen mode Exit fullscreen mode

This will do a similar thing to the arp table that was done to the nbtstat output, where it parses the table into objects and returns the hosts on the network.

The below class creates an instance of the above class for each adapter that is found.

Class _ArpStat
{
    [Object] $Alias
    [Object] $Table
    [Object] $Section
    [Object] $Output

    _ArpStat([Object[]]$Interface)
    {
        $This.Alias = ForEach ( $I in $Interface ) 
        {
            "Interface: {0} --- 0x{1:x}" -f $I.IPV4.IPAddress, $I.Index 
        }

        $This.Table   = arp -a
        $This.Section = @{ }
        $X            = -1

        ForEach ( $Line in $This.Table )
        {
            If ( $Line -in $This.Alias )
            {
                $X ++
                $This.Section.Add( $X,[_ArpTable]::New($Line))
            }

            ElseIf ( $Line -match "(static|dynamic)" )
            {
                $This.Section[$X].Hosts += [_ArpHostObject]::New($Line)
            }
        }

        $This.Output = $This.Section | % GetEnumerator | Sort-Object Name | % Value
    }
}
Enter fullscreen mode Exit fullscreen mode

This class is also parsing the broadcast and multicast objects into the table.

I left those in there intentionally, because when it comes to hidden devices on a network...?

Well, its worth taking a closer look at anomalies in those scopes if any exist.

Class _NetStatAddress
{
    Hidden [String]  $Item
    [String]    $IPAddress
    [String]         $Port

    _NetStatAddress([String]$Item)
    {
        $This.Item      = $Item

        If ( $Item -match "(\[.+\])" )
        {
            $This.IPAddress = [Regex]::Matches($Item,"(\[.+\])").Value
            $This.Port      = $Item.Replace($This.IPAddress,"")
            $This.IPAddress = $Item.TrimStart("[").Split("%")[0]
        }

        Else
        {
            $This.IPAddress = $This.Item.Split(":")[0]
            $This.Port      = $This.Item.Split(":")[1]
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

I believe that I am not using the above class at all, but it's probably worth checking out what its purpose was.

In this case, I was looking to create an object that could be usable for both the local address + port, as well as the remote address + port, when looking at the netstat table.

Netstat isn't particularly needed for connecting to an Active Directory instance, but its handy to have in a networking tool.

Class _NetStatObject
{
    Hidden [String]   $Line
    Hidden [Object]   $Item
    [String]      $Protocol
    [String]  $LocalAddress
    [String]     $LocalPort
    [String] $RemoteAddress
    [String]    $RemotePort
    [String]         $State
    [String]     $Direction

    _NetStatObject([String]$Line)
    {
        $This.Line          = $Line
        $This.Item          = $This.Line -Split " " | ? Length -gt 0
        $This.Protocol      = $This.Item[0]
        $This.LocalAddress  = $This.GetAddress($This.Item[1])
        $This.LocalPort     = $This.Item[1].Replace($This.LocalAddress + ":","")
        $This.RemoteAddress = $This.GetAddress($This.Item[2])
        $This.RemotePort    = $This.Item[2].Replace($This.RemoteAddress + ":","")
        $This.State         = $This.Item[3]
        $This.Direction     = $This.Item[4]
    }

    [String] GetAddress([String]$Item)
    {
        Return @( If ( $Item -match "(\[.+\])" )
        {
            [Regex]::Matches($Item,"(\[.+\])").Value
        }

        Else
        {
            $Item.Split(":")[0]
        })
    }
}

Enter fullscreen mode Exit fullscreen mode

This class probably looks a fair amount like a firewall entry..!

That is mainly because thats what it is...
...its the firewall and the interface to it.

However, saying that the class is a firewall entry is nonsense.

Because it isnt a firewall entry...
I know I just said that's what it is... but- its just a class that parses objects in netstat.

So, that's why it LOOKS like a firewall entry...
...because they're all pieces of session data.

7/Application
6/Presentation
5/Session
4/Transport
3/Network
2/Data Link
1/Physical

Get your 7 layer (OSI model/crunchwrap supreme) in order here...

Class _NetStat
{
    [Object] $Alias
    [Object] $Table
    [Object] $Section
    [Object] $Output

    _NetStat()
    {
        $This.Alias   = "Active Connections"
        $This.Table   = netstat -ant

        $This.Section = @{}
        $X            = -1

        ForEach ( $Line in $This.Table )
        {
            If ( $Line -match "(TCP|UDP)" )
            {
                $X ++
                $This.Section.Add($X,[_NetStatObject]::New($Line))
            }
        }

        $This.Output  = $This.Section | % GetEnumerator | Sort-Object Name | % Value 
    }
}
Enter fullscreen mode Exit fullscreen mode

This is where the netstat money could be made.

Turning all of its input and output, into tangible objects on a screen...?

Well that's where you could then begin to melt peoples minds with numerous possibilities...

Class _Controller
{
    Hidden [Object]      $VendorList
    Hidden [Object]    $NbtReference
    Hidden [Object]             $Nbt
    Hidden [Object]             $Arp
    [Object]              $Interface
    [Object]                $Network
    [Object]                $NetStat

    _Controller()
    {
        Write-Host "Collecting Network Adapter Information"

        $This.VendorList       = [_VendorList]::New("https://raw.githubusercontent.com/mcc85sx/FightingEntropy/master/scratch/VendorList.txt")
        $This.NBTReference     = [_NBTReference]::New().Output
        $This.Interface        = @( )

        ForEach ( $Interface in Get-NetIPConfiguration )
        {
            $Adapter           = [_NetInterface]::New($Interface)
            Write-Host ( "[+] {0}" -f $Adapter.Alias )
            $Adapter.GetVendor($This.VendorList)
            $This.Interface   += $Adapter
        }

        $This.NBT              = [_NbtStat]::New($This.Interface).Output
        $This.ARP              = [_ArpStat]::New($This.Interface).Output

        ForEach ( $Interface in $This.NBT )
        {
            ForEach ( $xHost in $Interface.Hosts )
            {
                $xHost.Service = $This.NBTReference | ? ID -match $xHost.ID | ? Type -eq $xHost.Type | % Service
            }
        }

        ForEach ( $I in 0..( $This.Interface.Count - 1 ) )
        {
            $IPAddress         = $This.Interface[$I].IPV4.IPAddress

            $xNbt              = $This.Nbt | ? IpAddress -match $IpAddress | % Hosts
            $xArp              = $This.Arp | ? IpAddress -match $IpAddress | % Hosts

            ForEach ( $Item in $xArp )
            {
                If ( $Item.Type -match "static" )
                {
                    $Item.Hostname = "-"
                    $Item.Vendor   = "-"
                }

                If ( $Item.Type -match "dynamic" )
                {
                    $Item.GetVendor($This.VendorList)

                    If ( !$Item.Vendor )
                    {
                        $Item.Vendor = "<unknown>"
                    }
                }
            }

            $This.Interface[$I].Load($xNbt,$xArp)
        }

        $This.Network = $This.Interface | ? { $_.IPV4.Gateway }

        $This.RefreshIPv4Scan()
        $This.RefreshNetStat()
    }

    RefreshNetStat()
    {
        $This.NetStat   = [_NetStat]::New().Output
    }

    RefreshIPv4Scan()
    {
        If (!$This.Network)
        {
            Throw "No available network found"
        }

        Else
        {
            Write-Host "Scanning available IPv4 network(s)..."
            ForEach ( $Item in $This.Network.IPv4.ScanV4() )
            {
                $This.Network.Arp | ? IpAddress -match $Item.IpAddress | % { $_.HostName = $Item.Hostname }
            }
        }
    }

    Report()
    {
        ForEach ( $Interface in $This.Interface )
        {
            $Interface | % { 

                Write-Theme @(
                "Interface [$($_.Alias)]",
                " ",
                "---- Host Information ---------------------------";
                @{
                    Hostname    = $_.Hostname
                    Alias       = $_.Alias
                    Index       = $_.Index
                    Description = $_.Description
                    Status      = $_.Status
                    MacAddress  = $_.MacAddress
                    Vendor      = $_.Vendor
                };
                " ",
                "---- IPv4 Information ---------------------------";
                ForEach ( $IPV4 in $_.IPV4 )
                {
                    @{ 
                        IPAddress   = $IPV4.IPAddress
                        Class       = $IPV4.Class
                        Prefix      = $IPV4.Prefix
                        Netmask     = $IPV4.Netmask
                        Network     = $IPV4.Network
                        Gateway     = $IPV4.Gateway
                        Subnet      = $IPV4.Subnet
                        Broadcast   = $IPV4.Broadcast
                        HostRange   = $IPV4.HostRange
                    }
                };
                " ",
                "---- IPv6 Information ---------------------------";
                ForEach ( $IPV6 in $_.IPV6 )
                {
                    @{
                        IPAddress = $IPV6.IPAddress
                        Prefix    = $IPV6.Prefix
                    }  
                })

                Start-Sleep -Seconds 2
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This thing up above, is the class that controls all of the interfaces that are collected/found, as well as all of the NBTstat, ARP, and Netstat tables, so that all of the information can be seen, and dynamically update itself depending on the selected item.

There is a method that will print the information found for each interface that is on the host machine...

Alt Text

There is a method that refreshes the NetStat panel.
There is a method that refreshes the IPV4 hosts found panel.

Alt Text

Class _FENetwork
{
    [Object] $Window
    [Object] $IO
    [Object] $Control

    _FENetwork()
    {
        $This.Window                        = Get-XamlWindow -Type FENetwork
        $This.IO                            = $This.Window.IO
        $This.Control                       = [_Controller]::New()
    }

    [Object] HostInfo([Object]$Interface)
    {
        Return @( 

            ("Hostname"    , $Interface.Hostname        ),
            ("Alias"       , $Interface.Alias           ),
            ("Index"       , $Interface.Index           ),
            ("Description" , $Interface.Description     ),
            ("Status"      , $Interface.Status          ),
            ("MacAddress"  , $Interface.MacAddress      ),
            ("Vendor"      , $Interface.Vendor          ) | % { [_DgList]::New($_[0],$_[1]) } 
        )
    }

    [Object] IPV4Info([Object]$Interface)
    {
        Return @( 

            ("Address"     , $Interface.IPV4.IPAddress  ),
            ("Class"       , $Interface.IPV4.Class      ),
            ("Prefix"      , $Interface.IPV4.Prefix     ),
            ("Netmask"     , $Interface.IPV4.Netmask    ),
            ("Network"     , $Interface.IPV4.Network    ),
            ("Gateway"     , $Interface.IPV4.Gateway    ),
            ("Broadcast"   , $Interface.IPV4.Broadcast  ) | % { [_DgList]::New($_[0],$_[1]) } 
        )
    }

    [Object] IPV6Info([Object]$Interface)
    {
        Return @( 

            ("Address"     , $Interface.IPV6.IPAddress  ),
            ("Prefix"      , $Interface.IPV6.Prefix     ) | % { [_DgList]::New($_[0],$_[1]) } 
        )
    }

    Stage([Object]$Interface)
    {
        $This.IO._HostInfo.ItemsSource      = $This.HostInfo($Interface)
        $This.IO._IPV4Info.ItemsSource      = $This.IPV4Info($Interface)
        $This.IO._IPV6Info.ItemsSource      = $This.IPV6Info($Interface)
        $This.IO._Nbt.ItemsSource           = $Interface.Nbt
        $This.IO._Arp.ItemsSource           = $Interface.Arp
    }
}
Enter fullscreen mode Exit fullscreen mode

This class here, combines the XAML and prepares some of the "code-behind", as well as providing all of the above classes, thrown together...
...into an object that has all of it's subcomponents able to be REREFERENCED and reset, according to the GUI and it's settings or actions.

When all of the above classes are wrapped into a function, then only this one single file will be needed to use and gain use out of.

Still, the final code that starts turning the LOOK of the page into actionable, able to be interacted with, code...?

If ( $GUI )
{
    $UI = [_FENetwork]::New()
    $UI.IO._Interfaces.ItemsSource              = $UI.Control.Interface.Alias
    $UI.IO._Interfaces.SelectedIndex            = 0

    $UI.Stage($UI.Control.Interface[($UI.IO._Interfaces.SelectedIndex)])

    $UI.IO._Interfaces.Add_SelectionChanged(
    {
        $UI.Stage($UI.Control.Interface[($UI.IO._Interfaces.SelectedIndex)])
    })

    $UI.IO.Cancel.Add_Click(
    {
        $UI.IO.DialogResult = $False
    })

    $UI.Window.Invoke()
}

Else 
{
    [_Controller]::New()
}
Enter fullscreen mode Exit fullscreen mode

Normally, the function 'Get-FENetwork -GUI' would be available but because I'm discussing it's content... that's why it's not wrapped in the function.

The variable $UI gets the class _FENetwork.

Just for the sake of drawing parallels here, using "$UI = [_FENetwork]::New()" is the same thing as "$UI = New-Object _FENetwork"

$UI.IO.Interfaces is a combobox control, setting its items source to the $UI.Control.Interface.Alias allows each item to be indexable/selectable by _name/alias.

$UI.IO._Interfaces.Add_SelectionChanged({codeblock}) is an action/event handler that is technically referencing itself, allowing easy databinding.

This function isn't complete, so options and taggings aren't applied.

The menu links don't go to web pages or message boxes like they do in the other utilities I have completed.

There's a great chance that the GUI you see in this lesson, well, at any point in time, it could easily be scrapped and rebuilt from scratch.

Since the XAML objects cover another shade of complexity, I'll reserve the remainder of this tutorial for where the GUI gets it's controls all rigged up.

-MC

Top comments (0)