<#
/// <copyright>
/// INTEL CONFIDENTIAL
///
/// Copyright 2014 Intel Corporation
///
/// This software and the related documents are Intel copyrighted materials, and your use of
/// them is governed by the express license under which they were provided to you ("License").
/// Unless the License provides otherwise, you may not use, modify, copy, publish, distribute,
/// disclose or transmit this software or the related documents without Intel's prior written
/// permission.
///
/// This software and the related documents are provided as is, with no express or implied
/// warranties, other than those that are expressly stated in the License.
///
/// </copyright>
#>

#User input arguments
param
(
    [Parameter(Mandatory=$true)]
    [string]$Action,

    [string]$ConfigPath,

    [switch]$BDF
)

#Global file variables
$Script:context
$Script:service
$Script:lockobj
$script:options
$Script:Adapters = $null
$Script:bSave = $false
$Script:bRestore = $false
$Script:bRemove = $false
$Script:bUpSave = $false
$Script:bUpRestore = $false
$Script:bANSInstalled = $true
$Script:colImportAdapters = $null
$Script:EnabledSystemAdapters = $null

#Save off input parameters to be used in functions
$Script:Action = $Action
$Script:BDF = $BDF
$Script:ConfigPath = $ConfigPath
$Script:ScriptPath = $null


#------------------------ Messages ------------------------
$Script:Messages = @{
    Usage = @"
Intel(R) SaveRestore.ps1
Copyright 2014 Intel Corporation. All rights reserved.

  Usage: SaveRestore.ps1 -Action -ConfigPath -BDF
  Example: SaveRestore.ps1 –Action save –ConfigPath C:\*File Path*\config.txt
  -Action is required. Valid values are 'save' and 'restore.'
  -ConfigPath is optional. It specifies the path and file name of the main configuration save file. If not specified, it is the script path and default file name (Saved_Config.txt).
  NOTE: The Saved_StaticIP.txt file name does not change and is always saved and restored from the script path.
  -BDF is optional. If specified during a restore, it will restore settings to adapters using their bus/device/function.
  The default configuration file names are Saved_Config.txt and Saved_StaticIP.txt.
"@;

    # Performing a save/restore of configuration file at $Script:ConfigPath
    RunningSaveRestore        = "Performing a save/restore of configuration file at";

    SettingOEMValue           = "Setting OEM custom value.";

    # Restoring primary and secondary adapters on $Team.TeamName
    RestoringTeamAdapters     = "Restoring primary and secondary adapters on";

    # Restoring $Team.TeamName / $Vlan.VlanName
    Restoring                 = "Restoring"

    RestoringAdapterIP        = "Restoring adapter IP settings."
    RestoringTeamIP           = "Restoring team IP settings."
    RestoringVLANIP           = "Restoring VLAN IP settings."
    RestoringAdapterSettings  = "Restoring adapter settings."
    RestoringTeamSettings     = "Restoring team settings."
    RestoringVLANSettings     = "Restoring VLAN settings."
    RestoringAdapterPartition = "Restoring adapter partition settings."

    NICPartitioningDetected   = "Detected a device configured for NIC partitioning. We recommend you use the -BDF option when restoring."

    PerformingSave            = "Saving the configuration."
    PerformingUpgradeSave     = "Upgrading and saving the configuration."
    PerformingRestore         = "Restoring the configuration."
    PerformingUpgradeRestore  = "Upgrading and restoring the configuration."
    PerformingRemove          = "Removing the team and VLAN device configuration."

    # $Script:ConfigPath file already exists. Do you want to overwrite it (y/n)?
    FileExistsPrompt          = "file already exists. Do you want to overwrite it (y/n)?"

    #------------------------ Errors ------------------------

    # No objects found for $class
    NoObjectsFound            = "No objects found for"

    AdminRequired             = "The save/restore script requires administrative rights. Please log in as an Administrator and try again."
    PROSetRequired            = "The save/restore script requires Intel® PROSet to be installed. Please install Intel PROSet and try again."

    # Could not find the restore configuration file at $Script:ConfigPath. Please provide a path to the configuration file.
    ConfigNotFoundPart1       = "Could not find the restore configuration file at"
    ConfigNotFoundPart2       = "Please provide a valid path to the configuration file."

    # Configuration path $FilePath does not exist. Please provide a valid path.
    ConfigPathDoesntExist1    = "A directory in"
    ConfigPathDoesntExist2    = "does not exist. Please provide a valid path."

    ConfigFileNotSpecified    = "No configuration file name specified. Please provide a valid file name."

    # Unable to set ... on ...
    UnableToSetPart1          = "Unable to set"
    UnableToSetPart2          = "on"

    #----------------------- Warnings -----------------------
    UnableToFindDevice        = "Unable to find Device. Please verify configuration file matches your system configuration."
    UnableToFindANS           = "Intel(R) ANS is not present on the system. Any Team and VLAN information contained in the configuration file will not be restored."
}

#-----------------------------------------------------[Functions]------------------------------------------------------


# Set WMI lock
Function BeginApply
{
    # retrieve instance
        $Script:service = Get-WMiobject -class IAnet_netservice -namespace root\intelncs2

    # create [in] parameters
        $inparams = $Script:service.GetMethodParameters("BeginApply")

    # spawn management object
        $Script:options = new-object System.Management.InvokeMethodOptions

    # add context for this script
        $Script:options.Context.Add('SaveRestoreApply', $true)
        $Script:options.Context.Add('GET_EXTENSIONS', $true)
        $Script:options.Context.Add('GET_EXT_KEYS_ONLY', $false)
        $Script:options.Context.Add('IANet_PartialData', 512)

    # obtain lock
        $Script:lockobj = $Script:service.InvokeMethod("BeginApply", $inparams, $Script:options)

    # this is necessary to set the lock into a new object
        $Script:context = new-object System.Management.ManagementNamedValueCollection

    # adding a new single named value to the collection with the value of the lock objects client handle
        $Script:context.Add('ClientSetId', [uint32] $Script:lockobj.ClientSetHandle)
        $Script:context.Add('SaveRestoreApply', $true)
}

# Remove WMI lock
Function ReleaseLock
{
    $inparams = $Script:service.GetMethodParameters("Apply")
    $inparams.ClientSetHandle=[uint32]$Script:lockobj.ClientSetHandle
    $null = $Script:Service.InvokeMethod("Apply", $inparams, $null)
}

Function NewObject ($class)
{
    $classdef = New-Object System.Management.ManagementClass $class
    # the new object options
    $classdef.Options.UseAmendedQualifiers = $true
    $classdef.Options.Context = $Script:context

    return $classdef
}

Function DeleteObject ($object)
{
    Write-Log $MyInvocation.MyCommand
    Write-Log $object
    # the delete options
    $deleteoptions = New-Object System.Management.DeleteOptions
    $deleteoptions.Context = $Script:context

    $object.Delete($deleteoptions)
}

Function SetSetting ($setting)
{
    Write-Log $MyInvocation.MyCommand
    Write-Log "`$setting = $setting"
    # the put options
    $putoptions = New-Object System.Management.PutOptions($Script:context)
    $putoptions.UseAmendedQualifiers = $true

    #perform put
    $null = $setting.Put($putoptions)
}

# Get specified class object from WMI
Function GetObject($class)
{
    $querystring = [string] "SELECT * FROM $class"
    $query = New-Object System.Management.ObjectQuery($querystring)

    # the enumeration options
    $enumerate_option = New-Object System.Management.EnumerationOptions
    $enumerate_option.UseAmendedQualifiers = $true
    $enumerate_option.Context = $Script:context

    # setup scope
    $DMiXPath = New-Object System.Management.ManagementPath
    $DMiXPath.NamespacePath = "root\intelncs2"
    $scope = New-Object System.Management.ManagementScope($DMiXPath)
    $scope.Connect()

    # the searcher for the object
    $searcher = New-Object System.Management.ManagementObjectSearcher($scope, $query, $enumerate_option)
    $collection = $searcher.Get()

    # If the Get() above fails, it won't be caught until the return happens. So catch it and display an appropriate message.
    try
    {
        return $collection
    }
    catch
    {
        $ErrorMessage = [string]$Messages.NoObjectsFound
        Write-Error "$ErrorMessage $class"
        Write-Log "$ErrorMessage $class"
        exit
    }
}

# Get associated objects given the object path and where condition
Function GetAssociated($path, $where)
{
    $querystring = [string] "ASSOCIATORS OF {$path} WHERE $where"

    $query = New-Object System.Management.ObjectQuery($querystring)

    # the enumeration options
    $enumerate_option = New-Object System.Management.EnumerationOptions
    $enumerate_option.UseAmendedQualifiers = $true
    $enumerate_option.Context = $Script:context

    # setup scope
    $DMiXPath = New-Object System.Management.ManagementPath
    $DMiXPath.NamespacePath = "root\intelncs2"
    $scope = New-Object System.Management.ManagementScope($DMiXPath)
    $scope.Connect()

    # the searcher for the object
    $searcher = New-Object System.Management.ManagementObjectSearcher($scope, $query, $enumerate_option)
    $collection = $searcher.Get()

    return $collection
}

# Invoke a method given it's name, class and parameters
Function InvokeMethod($Class,$Method,$ColParameterName,$ColParameterValue)
{
    $Invoke_option = New-Object System.Management.InvokeMethodOptions
    $Invoke_option.Context = $Script:context

    $params = $null
    if ($null -ne $ColParameterName)
    {
        $params = $Class.psbase.GetMethodParameters($Method)
        if ($ColParameterName.count -gt 1)
        {
            for($i=0; $i -lt $ColParameterName.count; $i++)
            {
                $params.psbase.SetPropertyValue($ColParameterName[$i],$ColParameterValue[$i])
            }
        }
        else
        {
            $params.psbase.SetPropertyValue($ColParameterName,$ColParameterValue)
        }
    }

    return $Class.psbase.InvokeMethod($Method,$params,$Invoke_option)
}

# Function used to write objects to config file.
Function WriteObjectToConfigFile($NewObjects,$Properties)
{
    Write-Log $MyInvocation.MyCommand
    Write-Log "`$NewObjects = $NewObjects"
    Write-Log "`$Properties = $Properties"
    if ($null -ne $NewObjects)
    {
        $ExportObject = $NewObjects | Select-Object $Properties | ConvertTo-Csv -NoTypeInformation
        $ExportObject = $ExportObject[1..$ExportObject.Count]
        WriteTextToConfigFile $ExportObject
    }
}

# Function used to write text to config file.
Function WriteTextToConfigFile($NewText)
{
    Write-Log $MyInvocation.MyCommand
    Write-Log "`$NewText = $NewText"
    $NewText | Out-File $Script:ConfigPath -Encoding utf8 -Append
}

# Function used to write objects to the IP config file.
Function WriteObjectToIPFile($NewObjects,$Properties)
{
    Write-Log $MyInvocation.MyCommand
    Write-Log "`$NewObjects = $NewObjects"
    Write-Log "`$Properties = $Properties"
    if ($null -ne $NewObjects)
    {
        $ExportObject = $NewObjects | Select-Object $Properties | ConvertTo-Csv -NoTypeInformation
        $ExportObject = $ExportObject[1..$ExportObject.Count]
        WriteTextToIPFile $ExportObject
    }
}

# Function used to write text to IP config file.
Function WriteTextToIPFile($NewText)
{
    Write-Log $MyInvocation.MyCommand
    Write-Log "`$NewText = $NewText"
    $NewText | Out-File $Script:ScriptPath\Saved_StaticIP.txt -Encoding utf8 -Append
}

# Function used to read objects from config file given a file section.
Function ReadFromConfigFile($FileSection)
{
    Write-Log $MyInvocation.MyCommand
    Write-Log "`$FileSection = $FileSection"
    $FileObjects=@()
    $FileObjects = Get-Content $Script:ConfigPath

    switch($FileSection)
    {
        "Adapters"
        {
            #Find the section for adapters and add 1 to not include AdaptersStart identifier
            $CsvObjectStart = [array]::indexof($FileObjects,"AdaptersStart") + 1
            #Find the end of the section and remove 1 to not include AdaptersEnd identifier
            $CsvEnd = [array]::indexof($FileObjects,"AdaptersEnd") - 1

            $colProperty = "Name","OriginalDisplayName","PermanentAddress","PCIDeviceID","SlotID"
            break
        }
        "AdapterSettings"
        {
            #Find the section for adapters and add 1 to not include AdapterSettingsStart identifier
            $CsvObjectStart = [array]::indexof($FileObjects,"AdapterSettingsStart") + 1
            #Find the end of the section and remove 1 to not include AdapterSettingsEnd identifier
            $CsvEnd = [array]::indexof($FileObjects,"AdapterSettingsEnd") - 1

            $colProperty = "Name","Caption","CurrentValue","CurrentValues","PermanentAddress","PCIDeviceID","SlotID"
            break
        }
        "OEMSetting"
        {
            #Find the section for adapters and add 1 to not include OEMSettingStart identifier
            $CsvObjectStart = [array]::indexof($FileObjects,"OEMSettingStart") + 1
            #Find the end of the section and remove 1 to not include OEMSettingEnd identifier
            $CsvEnd = [array]::indexof($FileObjects,"OEMSettingEnd") - 1

            $colProperty = "OEMCustomizeable"
            break
        }
        "PMSettings"
        {
            #Find the section for adapters and add 1 to not include PMSettingsStart identifier
            $CsvObjectStart = [array]::indexof($FileObjects,"PMSettingsStart") + 1
            #Find the end of the section and remove 1 to not include PMSettingsEnd identifier
            $CsvEnd = [array]::indexof($FileObjects,"PMSettingsEnd") - 1

            $colProperty = "Name","Caption","CurrentValue","CurrentValues","PermanentAddress","PCIDeviceID","SlotID"
            break
        }
        "Teams"
        {
            #Find the section for adapters and add 1 to not include TeamsStart identifier
            $CsvObjectStart = [array]::indexof($FileObjects,"TeamsStart") + 1
            #Find the end of the section and remove 1 to not include TeamsEnd identifier
            $CsvEnd = [array]::indexof($FileObjects,"TeamsEnd") - 1

            $colProperty = "TeamName","TeamMembers","TeamMode","PrimaryAdapter","SecondaryAdapter"
            break
        }
        "TeamSettings"
        {
            #Find the section for adapters and add 1 to not include TeamSettingsStart identifier
            $CsvObjectStart = [array]::indexof($FileObjects,"TeamSettingsStart") + 1
            #Find the end of the section and remove 1 to not include TeamSettingsEnd identifier
            $CsvEnd = [array]::indexof($FileObjects,"TeamSettingsEnd") - 1

            $colProperty = "TeamName","Caption","CurrentValue","CurrentValues"
            break
        }
        "Vlans"
        {
            #Find the section for adapters and add 1 to not include VlansStart identifier
            $CsvObjectStart = [array]::indexof($FileObjects,"VlansStart") + 1
            #Find the end of the section and remove 1 to not include VlansEnd identifier
            $CsvEnd = [array]::indexof($FileObjects,"VlansEnd") - 1

            $colProperty = "ParentName","VLANID","VLANNAME","ParentPermanentAddress","ParentPCIDeviceID","ParentSlotID"
            break
        }
        "VlanSettings"
        {
            #Find the section for adapters and add 1 to not include VlanSettingsStart identifier
            $CsvObjectStart = [array]::indexof($FileObjects,"VlanSettingsStart") + 1
            #Find the end of the section and remove 1 to not include VlanSettingsEnd identifier
            $CsvEnd = [array]::indexof($FileObjects,"VlanSettingsEnd") - 1

            $colProperty = "ParentName","Name","VLANID","Caption","CurrentValue","ParentPermanentAddress","ParentPCIDeviceID","ParentSlotID"
            break
        }
        "NICPARTSettings"
        {
            #Find the section for adapters and add 1 to not include NICPARTSettingsStart identifier
            $CsvObjectStart = [array]::indexof($FileObjects,"NICPARTSettingsStart") + 1
            #Find the end of the section and remove 1 to not include NICPARTSettingsEnd identifier
            $CsvEnd = [array]::indexof($FileObjects,"NICPARTSettingsEnd") - 1

            $colProperty = "Name","PartitionNumber","Identifier","MinBWPercent","MaxBWPercent"
            break
        }
        Default
        {
            return
        }
    }

    #If no items were found in the provided section return nothing
    if ($CsvEnd -lt $CsvObjectStart)
    {
        return

    }
    #Else return only the provided section and convert the text into objects
    else
    {
        return $FileObjects[$CsvObjectStart..$CsvEnd] | ConvertFrom-Csv -Header $colProperty
    }
}

# Function used to read objects from config file.
Function ReadFromIPFile($FileSection)
{
    Write-Log $MyInvocation.MyCommand
    Write-Log "`$FileSection = $FileSection"
    $FileObjects=@()
    $FileObjects = Get-Content $Script:ScriptPath\Saved_StaticIP.txt

    switch($FileSection)
    {
        "AdapterIPSettings"
        {
            #Find the section for adapters and add 1 to not include AdapterIPSettingsStart identifier
            $CsvObjectStart = [array]::indexof($FileObjects,"AdapterIPSettingsStart") + 1
            #Find the end of the section and remove 1 to not include AdapterIPSettingsEnd identifier
            $CsvEnd = [array]::indexof($FileObjects,"AdapterIPSettingsEnd") - 1

            $colProperty = "Name","Caption","CurrentValue","CurrentValues","PermanentAddress","PCIDeviceID","SlotID"
            break
        }
        "TeamIPSettings"
        {
            #Find the section for adapters and add 1 to not include TeamIPSettingsStart identifier
            $CsvObjectStart = [array]::indexof($FileObjects,"TeamIPSettingsStart") + 1
            #Find the end of the section and remove 1 to not include TeamIPSettingsEnd identifier
            $CsvEnd = [array]::indexof($FileObjects,"TeamIPSettingsEnd") - 1

            $colProperty = "TeamName","Caption","CurrentValue","CurrentValues"
            break
        }
        "VlanIPSettings"
        {
            #Find the section for adapters and add 1 to not include VlanIPSettingsStart identifier
            $CsvObjectStart = [array]::indexof($FileObjects,"VlanIPSettingsStart") + 1
            #Find the end of the section and remove 1 to not include VlanIPSettingsEnd identifier
            $CsvEnd = [array]::indexof($FileObjects,"VlanIPSettingsEnd") - 1

            $colProperty = "ParentName","VLANID","Caption","CurrentValue","CurrentValues","ParentPermanentAddress","ParentPCIDeviceID","ParentSlotID"
            break
        }
        Default
        {
            return
        }
    }

    # If no items were found in the provided section return nothing
    if ($CsvEnd -lt $CsvObjectStart)
    {
        return
    }
    # Else return only the provided section and convert the text into objects
    else
    {
        return $FileObjects[$CsvObjectStart..$CsvEnd] | ConvertFrom-Csv -Header $colProperty
    }
}

# Add the parent adapter identifiers to the setting objects
Function AddParentIDs($Setting)
{
    Write-Log $MyInvocation.MyCommand
    Write-Log "`$Setting = $Setting"
    #Add the PermanentAddress, PCIDeviceID and SlotID to the settings object
    $SettingAdapter = $Script:Adapters | Where-Object {$_.DeviceID -eq $Setting.ParentId}
    if ($SettingAdapter)
    {
        $Setting | Add-Member -Name "PermanentAddress" -Value  $SettingAdapter.PermanentAddress -MemberType NoteProperty
        $Setting | Add-Member -Name "PCIDeviceID" -Value  $SettingAdapter.PCIDeviceID -MemberType NoteProperty
        $AdapterSlotID =  $SettingAdapter.SlotID.split(":")[0..2] #return only the first three elements in array
        $AdapterBDF = [string]::Join(':',$AdapterSlotID)
        $Setting | Add-Member -Name "SlotID" -Value  $AdapterBDF -MemberType NoteProperty
    }
}

Function PrintUsage
{
    Write-Host $Messages.Usage
    exit
}

Function CheckForAdminRights
{
    $winIdent = [Security.Principal.WindowsIdentity]::GetCurrent()
    $WinPrinc = [Security.Principal.WindowsPrincipal] $winIdent
    $AdminId  = [Security.Principal.WindowsBuiltInRole] "Administrator"
    if (-Not $WinPrinc.IsInRole($AdminId))
    {
        Write-Error $Messages.AdminRequired
        exit
    }
}

Function CheckForDMiXInstall
{
    Write-Log $MyInvocation.MyCommand
    $DMiXInstall = Get-ItemProperty -Path HKLM:\SOFTWARE\Intel\Network_Services\DMIX -Name InstalledDMIX -ErrorAction SilentlyContinue

    if ((!$DMiXInstall) -or ($DMiXInstall.InstalledDMIX -ne 1))
    {
        Write-Error $Messages.PROSetRequired
        exit
    }
}

Function CheckForANSInstall
{
    Write-Log $MyInvocation.MyCommand
    $ANSInstall = Get-ItemProperty -Path HKLM:\SOFTWARE\Intel\Network_Services\DMIX -Name InstalledDMIX_ANS -ErrorAction SilentlyContinue

    if ((!$ANSInstall) -or ($ANSInstall.InstalledDMIX_ANS -ne 1))
    {
        $Script:bANSInstalled = $false
    }
}

Function CheckIfConfigFileExists
{
    Write-Log $MyInvocation.MyCommand
    if (!(Test-Path $Script:ConfigPath -PathType Leaf))
    {
        $ErrorMessagePart1 = [string]$Messages.ConfigNotFoundPart1
        $ErrorMessagePart2 = [string]$Messages.ConfigNotFoundPart2

        Write-Error  "$ErrorMessagePart1 $Script:ConfigPath. $ErrorMessagePart2"
        exit
    }
}

Function CheckIfConfigShouldBeOverwritten
{
    Write-Log $MyInvocation.MyCommand
    if (!(Test-Path $Script:ConfigPath.ToString() -PathType Container))
    {
        $FileExistsString = [string]$Messages.FileExistsPrompt
        $confirmation = Read-Host -Prompt "$Script:ConfigPath $FileExistsString"
        if ($confirmation -notmatch "[yY]")
        {
            exit
        }
    }
}

Function Write-Log($string)
{
    $timestamp = "[$(Get-Date -Format G)] "
    $string = $timestamp + $string
    Write-Verbose -Message $string
}

# Sets a global variable for the configuration file paths to be saved/restored from.
Function SetupSaveRestoreLocation
{
    Write-Log $MyInvocation.MyCommand
    $Script:ScriptPath = Get-Location

    # If the user did not specify a path, use the scripts path
    if ($Script:bUpSave -eq $true -or $Script:bUpRestore -eq $true)
    {
        $Script:ConfigPath = Get-Location
        $Script:ConfigPath = $Script:ConfigPath + "\Upgrade_Saved_Config.txt"
    }
    elseif (!$Script:ConfigPath)
    {
        $Script:ConfigPath = Get-Location
        $Script:ConfigPath = $Script:ConfigPath + "\Saved_Config.txt"

        #Check if the file exist during a save
        if ($Script:bSave -eq $true -and (Test-Path $Script:ConfigPath -PathType Leaf))
        {
            CheckIfConfigShouldBeOverwritten
        }
    }
    else
    {
        #Only prompt to overwrite if we are doing save and file exists
        if (($Script:bSave -eq $true -or $Script:bUpSave -eq $true) -and (Test-Path $Script:ConfigPath -PathType Leaf))
        {
            CheckIfConfigShouldBeOverwritten
        }

        #Current dir is default
        $FilePath = "."
        #Find position of the last backslash before the filename
        $FileNamePos = $Script:ConfigPath.ToString().LastIndexOf("\")
        #pos = -1 means ConfigPath contains just the file name - use current dir in this case
        if ($FileNamePos -ne -1)
        {
            #Separate the filename from the path to verify path exists
            $FilePath = $Script:ConfigPath.ToString().Substring(0, $FileNamePos)
        }

        #Check that config path exists
        if (!(Test-Path $FilePath -PathType Container))
        {
            $ErrorMessagePart1 = [string]$Messages.ConfigPathDoesntExist1
            $ErrorMessagePart2 = [string]$Messages.ConfigPathDoesntExist2
            Write-Error "$ErrorMessagePart1 $FilePath $ErrorMessagePart2"
            exit
        }

        #Check the input of the config file when Saving
        if ($Script:bSave -eq $true -or $Script:bUpSave -eq $true)
        {
            Try
            {
                Out-File $Script:ConfigPath  -ErrorAction Stop
            }
            Catch
            {
                Write-Error $Messages.ConfigFileNotSpecified
                Write-Log $Messages.ConfigFileNotSpecified
                exit
            }
        }
        #Check the input of the config file when Restoring
        elseif (!(Test-Path $Script:ConfigPath))
        {
            Write-Error $Messages.ConfigFileNotSpecified
            exit
        }
    }

    Write-Host $Messages.RunningSaveRestore $Script:ConfigPath
}

# Get the present and enabled adapters on the system.
Function GetAdaptersOnSystem
{
    Write-Log $MyInvocation.MyCommand
    #Only store adapters that have a status of 3 (which means enabled)
    $Script:EnabledSystemAdapters = GetObject "IANet_PhysicalEthernetAdapter" | Where-Object {($_.StatusInfo -eq 3)}  | Sort-Object -Property Name
    #Only store adapters that support DMiX or Extended DMiX capability or if it is intel vendor capable and this is an upgrade.
    $Script:Adapters = $Script:EnabledSystemAdapters | Where-Object {($_.Capabilities -eq 73) -or ($_.Capabilities -eq 74) -or (($_.Capabilities -eq 47) -and (($bUpSave -eq $true) -or ($bUpRestore -eq $true)))}
    #Save Teamable adapters to be referenced for teams
}

# Remove any present configuration files
Function RemoveOldFiles
{
    Write-Log $MyInvocation.MyCommand
    # check if the file exists before trying to remove it
    if (Test-Path $Script:ConfigPath -PathType Leaf)
    {
        Remove-Item $Script:ConfigPath
    }
    if (Test-Path $Script:ScriptPath\Saved_StaticIP.txt)
    {
        Remove-Item $Script:ScriptPath\Saved_StaticIP.txt
    }
}

# Save adapters on system to the configuration file
Function SaveAdapters
{
    Write-Log $MyInvocation.MyCommand
    WriteTextToConfigFile "AdaptersStart"

    $colProperty = "Name","OriginalDisplayName","PermanentAddress","PCIDeviceID","SlotID"
    WriteObjectToConfigFile $Script:EnabledSystemAdapters $colProperty

    WriteTextToConfigFile "AdaptersEnd"
    WriteTextToConfigFile ""
}

# Save OEM customization value
Function SaveOEMCustomizeableSetting
{
    Write-Log $MyInvocation.MyCommand
    WriteTextToConfigFile "OEMSettingStart"
    $OEMSetting = Get-ItemProperty -Path HKLM:\SOFTWARE\Intel\Network_Services\NCS2 -Name OEMCustomizeable -ErrorAction SilentlyContinue

    # If a value is present save it to the config file
    if ($null -ne $OEMSetting)
    {
        WriteObjectToConfigFile $OEMSetting OEMCustomizeable
    }

    WriteTextToConfigFile "OEMSettingEnd"
    WriteTextToConfigFile ""
}

# Save any legacy power management settings
Function SavePowerManagementSettings
{
    Write-Log $MyInvocation.MyCommand
    $colProperty = "Name","Caption","CurrentValue",@{expression={$_.CurrentValues -join ","};label="CurrentValues"},"PermanentAddress","PCIDeviceID","SlotID"

    WriteTextToConfigFile "PMSettingsStart"

    foreach ($Adapter in $Script:Adapters)
    {
        try
        {
            #Get the Power Management settings for adapter
            $PowerSettingValues = InvokeMethod $Adapter GetPowerUsageOptions $null $null
            $PowerSettingProperties = @{Name = $Adapter.Name;
                                        CurrentValue = $PowerSettingValues.AutoPowerSaveModeEnabled;
                                        CurrentValues = {};
                                        PermanentAddress = $Adapter.PermanentAddress;
                                        PCIDeviceID = $Adapter.PCIDeviceID;
                                        SlotID = $Adapter.SlotID}

            # Check each Power Management setting to see if it NULL before trying to save it to the config file
            # if there is a value, create a custom object and save it to the config file
            if ($null -ne $PowerSettingValues.AutoPowerSaveModeEnabled)
            {
                $Setting = New-Object PSObject -Property $PowerSettingProperties
                $Setting | Add-Member -Name "Caption" -Value  "AutoPowerSaveModeEnabled" -MemberType NoteProperty
                WriteObjectToConfigFile $Setting $colProperty
            }
            if ($null -ne $PowerSettingValues.ReduceSpeedOnPowerDown)
            {
                $Setting = New-Object PSObject -Property $PowerSettingProperties
                $Setting | Add-Member -Name "Caption" -Value  "ReduceSpeedOnPowerDown" -MemberType NoteProperty
                WriteObjectToConfigFile $Setting $colProperty
            }
            if ($null -ne $PowerSettingValues.SmartPowerDown)
            {
                $Setting = New-Object PSObject -Property $PowerSettingProperties
                $Setting | Add-Member -Name "Caption" -Value  "SmartPowerDown" -MemberType NoteProperty
                WriteObjectToConfigFile $Setting $colProperty
            }
            if ($null -ne $PowerSettingValues.SavePowerNowEnabled)
            {
                $Setting = New-Object PSObject -Property $PowerSettingProperties
                $Setting | Add-Member -Name "Caption" -Value  "SavePowerNowEnabled" -MemberType NoteProperty
                WriteObjectToConfigFile $Setting $colProperty
            }
            if ($null -ne $PowerSettingValues.EnhancedASPMPowerSaver)
            {
                $Setting = New-Object PSObject -Property $PowerSettingProperties
                $Setting | Add-Member -Name "Caption" -Value  "EnhancedASPMPowerSaver" -MemberType NoteProperty
                WriteObjectToConfigFile $Setting $colProperty
            }
        }
        catch
        {
            Write-Log "Unable to Save Power Management Settings"
        }
    }

    WriteTextToConfigFile "PMSettingsEnd"
    WriteTextToConfigFile ""
}

# Some settings need to be saved in a certain order, this function stops them from being saved now so they can be saved later by returning
# whether it should be saved immidiately or later (using true or false).
Function SaveAdapterSettingLater($Setting, $bIPSetting)
{
    Write-Log $MyInvocation.MyCommand
    Write-Log "`$Setting = $Setting"
    Write-Log "`$bIPSetting = $bIPSetting"
    $bRet = $true

    # Don't save the settings now if it is an IP, DCB, performance profile, SRIOV, VMQueues, or NUMVF setting
    if (($bIPSetting -eq $false) -and
        ($Setting.GroupId -ne 12 ) -and
        ($Setting.GroupId -ne 8 ) -and
        ($Setting.Caption -ne "PerformanceProfile") -and
        ($Setting.Caption -ne "*FlowControl") -and
        ($Setting.Caption -ne "*PriorityVLANTag") -and
        ($Setting.Caption -ne "*SRIOV") -and
        ($Setting.Caption -ne "VMQueues") -and
        ($Setting.Caption -ne "*NumVFs"))
    {
        $bRet = $false
    }

    return $bRet
}

# Check if the given setting is an IP setting and save it in the IP config file and return if it is an IP setting (true or false).
# Depending on the device different properties of the object need to be saved.
Function SaveIPSetting($Setting,$DeviceType,$EnabledDHCP,$bSaveIPv6Settings)
{
    Write-Log $MyInvocation.MyCommand
    Write-Log "`$DeviceType = $DeviceType"
    Write-Log "`$Setting = $Setting"
    Write-Log "`$bEnabledDHCP = $EnabledDHCP"
    Write-Log "`$bSaveIPv6Settings = $bSaveIPv6Settings"
    $bIPSetting = $false
    $bSaveIpSetting = $false

    # Check if the passed in setting is one of these IP settings.
    # Some IP settings need DHCP enabled to restore.
    switch($Setting.Caption)
    {
        "IPAddress"
        {
            $bIPSetting = $true
            if ($EnabledDHCP -eq 0)
            {
                $bSaveIpSetting = $true
            }
        }
        "IPv4Address"
        {
            $bIPSetting=$true
            if ($EnabledDHCP -eq 0)
            {
                $bSaveIpSetting = $true
            }
        }
        "IPv6Address"
        {
            $bIPSetting=$true
            if ($bSaveIPv6Settings -eq $true)
            {
                $bSaveIpSetting = $true
            }
        }
        "PrefixLength"
        {
            $bIPSetting=$true
            if ($bSaveIPv6Settings -eq $true)
            {
                $bSaveIpSetting = $true
            }
        }
        "SubnetMask"
        {
            $bIPSetting=$true
            if ($EnabledDHCP -eq 0)
            {
                $bSaveIpSetting = $true
            }
        }
        "DefaultGateway"
        {
            $bIPSetting=$true
            if ($EnabledDHCP -eq 0)
            {
                $bSaveIpSetting = $true
            }
        }
        "DefaultGatewayIPv6"
        {
            $bIPSetting=$true
            if ($bSaveIPv6Settings -eq $true)
            {
                $bSaveIpSetting = $true
            }
        }
        "NameServer"
        {
            $bIPSetting=$true
            if($EnabledDHCP -eq 0)
            {
                $bSaveIpSetting = $true
            }
        }
        "NameServerList"
        {
            $bIPSetting=$true
            $bSaveIpSetting = $true
        }
        "NetbiosOptions"
        {
            $bIPSetting=$true
            $bSaveIpSetting = $true
        }
    }

    # Save IP settings with different properties depending on the device type
    if ($bSaveIpSetting -eq $true -and $DeviceType -eq "Adapter")
    {
        $colProperty = "Name","Caption","CurrentValue",@{expression={$_.CurrentValues -join ","};label="CurrentValues"},"PermanentAddress","PCIDeviceID","SlotID"
        WriteObjectToIPFile $Setting $colProperty
    }
    elseif ($bSaveIpSetting -eq $true -and $DeviceType -eq "Team")
    {
        $colProperty = "TeamName","Caption","CurrentValue",@{expression={$_.CurrentValues -join ","};label="CurrentValues"}
        WriteObjectToIPFile $Setting $colProperty
    }
    elseif ($bSaveIpSetting -eq $true -and $DeviceType -eq "Vlan")
    {
        $colProperty = "ParentName","VLANID","Caption","CurrentValue",@{expression={$_.CurrentValues -join ","};label="CurrentValues"},"ParentPermanentAddress","ParentPCIDeviceID","ParentSlotID"
        WriteObjectToIPFile $Setting $colProperty
    }

    return $bIPSetting
}

# Save the adapter settings
Function SaveAdapterSettings()
{
    Write-Log $MyInvocation.MyCommand
    $colProperty = "Name","Caption","CurrentValue",@{expression={$_.CurrentValues -join ","};label="CurrentValues"},"PermanentAddress","PCIDeviceID","SlotID"

    # Save power management settings
    SavePowerManagementSettings

    WriteTextToConfigFile "AdapterSettingsStart"
    WriteTextToIPFile "AdapterIPSettingsStart"

    foreach ($Adapter in $Script:Adapters)
    {
        # Get the settings associated with the adapter
        $colSettings = GetAssociated $Adapter.path.path "ResultClass = IANet_AdapterSetting"
        $colSettings = $colSettings  | Sort-Object -Property Name,Caption

        # Check status of EnablDHCP for IP settings later
        $EnableDHCPCol = $colSettings | Where-Object {($_.Caption -eq "EnableDHCP")} | Select-Object $colProperty
        $EnableDHCPv6Col = $colSettings | Where-Object {($_.Caption -eq "EnableDHCPv6")} | Select-Object $colProperty
        $ManualIPv6Col = $colSettings | Where-Object {($_.Caption -eq "ManualIPv6")} | Select-Object $colProperty

        #Get the DHCP enable value for the specific adapter
        $EnableDHCP = $EnableDHCPCol | Where-Object {($_.Name -eq $Adapter.Name)}

        # Get the DHCPv6 enable value, and the ManualIPv6 value
        # EnableDHCPv6 is 0 (disabled) only if the "Managed Address Configuration Flag" and the "Other Stateful Configuration Flag" are both 0
        # (see https://blogs.technet.microsoft.com/teamdhcp/2009/03/03/dhcpv6-understanding-of-address-configuration-in-automatic-mode-and-installation-of-dhcpv6-server/)
        # ManualIPv6 is 1 (manual) only if the PrefixOrigin and SuffixOrigin of any IPv6 address are both "Manual" - other values indicate autoconfiguration
        $EnableDHCPv6 = $EnableDHCPv6Col | Where-Object {($_.Name -eq $Adapter.Name)}
        $ManualIPv6 = $ManualIPv6Col | Where-Object {($_.Name -eq $Adapter.Name)}

        $bSaveIPv6Settings = $false
        #if EnableDHCPv6 = 0 and ManualIPv6 = 1, we want to save IPv6 addresses and settings. Otherwise, all IPv6 addresses were autoconfigured and we don't need to save/restore them
        if($EnableDHCPv6.CurrentValue -eq 0 -and $ManualIPv6.CurrentValue -eq 1)
        {
            $bSaveIPv6Settings = $true
        }

        foreach ($Setting in $colSettings)
        {
            AddParentIDs $Setting

            #check if setting is an IP setting save them in the IP config file instead of the Saved_Config file
            $bIPSetting = SaveIPSetting $Setting "Adapter" $EnableDHCP.CurrentValue $bSaveIPv6Settings

            #Check to see if the setting should be saved later
            $bRet = SaveAdapterSettingLater $Setting $bIPSetting
            if ($bRet -eq $false)
            {
                WriteObjectToConfigFile $Setting $colProperty
            }
        }

        # Check if DCB is being updated and if so, don't save the settings so the default values are restored
        $RestoreDCB = $true
        $FCoEUpdate = Get-ItemProperty -Path HKLM:\SOFTWARE\Intel\Prounstl -Name DCB_Update_FCoE  -ErrorAction SilentlyContinue
        if ($null -ne $FCoEUpdate)
        {
            if ($FCoEUpdate.DCB_Update_FCoE -eq 1)
            {
                #FCoE is changing don't save settings
                $RestoreDCB = $false
            }
        }
        $iSCSIUpdate = Get-ItemProperty -Path HKLM:\SOFTWARE\Intel\Prounstl -Name DCB_Update_iSCSI  -ErrorAction SilentlyContinue
        if ($null -ne $iSCSIUpdate)
        {
            if ($iSCSIUpdate.DCB_Update_iSCSI -eq 1)
            {
                #iSCSI is changing don't save settings
                $RestoreDCB = $false
            }
        }

        #Save *SRIOV after *VMQ
        $SRIOVSetting = $colSettings | Where-Object {($_.Caption -eq "*SRIOV")}
        WriteObjectToConfigFile $SRIOVSetting $colProperty

        #Save DCB Settings if this is not an upgrade or if it is an upgrade, and we are modifying DCB
        if (($Script:bUpSave -eq $false) -or ($RestoreDCB -eq $true))
        {
            $colDCBSettings = $colSettings | Where-Object {($_.GroupId -eq 12) -or ($_.GroupId -eq 8)}
            WriteObjectToConfigFile $colDCBSettings $colProperty
        }

        #Save the performance profile
        $ProfileSetting = $colSettings | Where-Object {($_.Caption -eq "PerformanceProfile")}
        WriteObjectToConfigFile $ProfileSetting $colProperty

        #Save the FlowControl after performance profile and DCB
        $FlowControlSetting = $colSettings | Where-Object {($_.Caption -eq "*FlowControl")}
        WriteObjectToConfigFile $FlowControlSetting $colProperty

        #Save the PriorityVLANTag after performance profile and DCB
        $PriorityVLANTagSetting = $colSettings | Where-Object {($_.Caption -eq "*PriorityVLANTag")}
        WriteObjectToConfigFile $PriorityVLANTagSetting $colProperty

        #Save VMQueues and *NUMVFs last
        $VMQQueuesSetting = $colSettings | Where-Object {($_.Caption -eq "VMQueues")}
        WriteObjectToConfigFile $VMQQueuesSetting $colProperty
        $NumVFsSetting = $colSettings | Where-Object {($_.Caption -eq "*NumVFs")}
        WriteObjectToConfigFile $NumVFsSetting $colProperty
    }

    WriteTextToConfigFile "AdapterSettingsEnd"
    WriteTextToConfigFile ""

    WriteTextToIPFile "AdapterIPSettingsEnd"
    WriteTextToIPFile ""
}

#Save team information
Function SaveTeams
{
    Write-Log $MyInvocation.MyCommand
    #Get current teams on system
    $colProperty = "TeamName","TeamMembers","TeamMode","PrimaryAdapter","SecondaryAdapter"
    $colItems = GetObject "IANet_TeamOfAdapters" | Select-Object $colProperty | Sort-Object -Property TeamName


    WriteTextToConfigFile "TeamsStart"

    # if there are teams on system save the team information to the config file.
    if ($colItems)
    {
        # convert Member arrays into a "single" value to save
        foreach ($item in $colItems)
        {
            $TeamMembers = $item.TeamMembers
            $item.TeamMembers = @()
            for (($i = 0); $i -lt $TeamMembers.Count; $i++)
            {
                $adapter = GetObject IANet_PhysicalEthernetAdapter | Where-Object {($_.OriginalDisplayName -eq $TeamMembers[$i])}
                $PermanentAddress = $adapter | Select-Object -ExpandProperty "PermanentAddress"
                $PCIDeviceID = $adapter | Select-Object -ExpandProperty "PCIDeviceID"
                $SlotID = $adapter | Select-Object -ExpandProperty "SlotID"
                $item.TeamMembers +=  $TeamMembers[$i] + "," + $PermanentAddress + "," + $PCIDeviceID + "," + $SlotID
            }
            $item.TeamMembers = $item.TeamMembers -join '|'
        }

        WriteObjectToConfigFile $colItems *
    }

    WriteTextToConfigFile "TeamsEnd"
    WriteTextToConfigFile ""
}

# Save team settings
Function SaveTeamSettings
{
    Write-Log $MyInvocation.MyCommand
    # Get the current team settings
    $colProperty = "TeamName","Caption","CurrentValue",@{expression={$_.CurrentValues -join ","};label="CurrentValues"}
    $colSettings = GetObject "IANet_TeamSetting" | Sort-Object -Property Name,Caption

    WriteTextToConfigFile "TeamSettingsStart"
    WriteTextToIPFile "TeamIPSettingsStart"

    # Check status of EnablDHCP for IP settings later
    $EnableDHCPCol = $colSettings | Where-Object {($_.Caption -eq "EnableDHCP")} | Select-Object $colProperty
    $EnableDHCPv6Col = $colSettings | Where-Object {($_.Caption -eq "EnableDHCPv6")} | Select-Object $colProperty
    $ManualIPv6Col = $colSettings | Where-Object {($_.Caption -eq "ManualIPv6")} | Select-Object $colProperty

    foreach ($Setting in $colSettings)
    {
        # Get the DHCP enable value for the specific Team
        $EnableDHCP = $EnableDHCPCol | Where-Object {($_.TeamName -eq $Setting.Name)}

        # Get the DHCPv6 enable value, and the ManualIPv6 value
        # EnableDHCPv6 is 0 (disabled) only if the "Managed Address Configuration Flag" and the "Other Stateful Configuration Flag" are both 0
        # (see https://blogs.technet.microsoft.com/teamdhcp/2009/03/03/dhcpv6-understanding-of-address-configuration-in-automatic-mode-and-installation-of-dhcpv6-server/)
        # ManualIPv6 is 1 (manual) only if the PrefixOrigin and SuffixOrigin of any IPv6 address are both "Manual" - other values indicate autoconfiguration
        $EnableDHCPv6 = $EnableDHCPv6Col | Where-Object {($_.TeamName -eq $Setting.Name)}
        $ManualIPv6 = $ManualIPv6Col | Where-Object {($_.TeamName -eq $Setting.Name)}

        $bSaveIPv6Settings = $false
        # if EnableDHCPv6 = 0 and ManualIPv6 = 1, we want to save IPv6 addresses and settings. Otherwise, all IPv6 addresses were autoconfigured and we don't need to save/restore them
        if ($EnableDHCPv6.CurrentValue -eq 0 -and $ManualIPv6.CurrentValue -eq 1)
        {
            $bSaveIPv6Settings = $true
        }

        # Save the IP Settings in the IP config file
        $bIPSetting = SaveIPSetting $Setting "Team" $EnableDHCP.CurrentValue $bSaveIPv6Settings

        if ($bIPSetting -eq $false)
        {
            WriteObjectToConfigFile $Setting $colProperty
        }
    }

    WriteTextToConfigFile "TeamSettingsEnd"
    WriteTextToConfigFile ""

    WriteTextToIPFile "TeamIPSettingsEnd"
    WriteTextToIPFile ""
}

# Save vlan information
Function SaveVlans
{
    Write-Log $MyInvocation.MyCommand
    #Get the vlans on the system
    $colProperty = "ParentName", "VLANID", "VLANNAME", "ParentPermanentAddress", "ParentPCIDeviceID", "ParentSlotID"
    $colVlans = GetObject "IANet_VLAN" | Sort-Object -Property Parent,VLANID


    WriteTextToConfigFile "VlansStart"

    # Save untagged vlan last if there are vlans on the system
    if ($colVlans)
    {
        $colTaggedVlans = $colVlans | Where-Object {($_.VLANID -ne 0)}
        WriteObjectToConfigFile $colTaggedVlans $colProperty
        $colUnTaggedVlans = $colVlans | Where-Object {($_.VLANID -eq 0)}
        WriteObjectToConfigFile $colUnTaggedVlans $colProperty
    }

    WriteTextToConfigFile "VlansEnd"
    WriteTextToConfigFile ""
}

# Save vlan settings
Function SaveVlanSettings
{
    Write-Log $MyInvocation.MyCommand
    #Get vlan settings on system
    $colProperty = "ParentName","Name","VLANID","Caption","CurrentValue","ParentPermanentAddress","ParentPCIDeviceID","ParentSlotID"
    $colSettings = GetObject "IANet_VLANSetting" | Sort-Object -Property Name,Caption

    WriteTextToConfigFile "VlanSettingsStart"
    WriteTextToIPFile "VlanIPSettingsStart"

    # Check status of EnablDHCP for IP settings later
    $EnableDHCPCol = $colSettings | Where-Object {($_.Caption -eq "EnableDHCP")} | Select-Object $colProperty
    $EnableDHCPv6Col = $colSettings | Where-Object {($_.Caption -eq "EnableDHCPv6")} | Select-Object $colProperty
    $ManualIPv6Col = $colSettings | Where-Object {($_.Caption -eq "ManualIPv6")} | Select-Object $colProperty

    foreach ($Setting in $colSettings)
    {
        # Get the DHCP enable value for the specific adapter
        $EnableDHCP = $EnableDHCPCol | Where-Object {($_.Name -eq $Setting.Name)}

        # Get the DHCPv6 enable value, and the ManualIPv6 value
        # EnableDHCPv6 is 0 (disabled) only if the "Managed Address Configuration Flag" and the "Other Stateful Configuration Flag" are both 0
        # (see https://blogs.technet.microsoft.com/teamdhcp/2009/03/03/dhcpv6-understanding-of-address-configuration-in-automatic-mode-and-installation-of-dhcpv6-server/)
        # ManualIPv6 is 1 (manual) only if the PrefixOrigin and SuffixOrigin of any IPv6 address are both "Manual" - other values indicate autoconfiguration
        $EnableDHCPv6 = $EnableDHCPv6Col | Where-Object {($_.Name -eq $Setting.Name)}
        $ManualIPv6 = $ManualIPv6Col | Where-Object {($_.Name -eq $Setting.Name)}

        $bSaveIPv6Settings = $false
        # if EnableDHCPv6 = 0 and ManualIPv6 = 1, we want to save IPv6 addresses and settings. Otherwise, all IPv6 addresses were autoconfigured and we don't need to save/restore them
        if ($EnableDHCPv6.CurrentValue -eq 0 -and $ManualIPv6.CurrentValue -eq 1)
        {
            $bSaveIPv6Settings = $true
        }

        # Save the IP Settings in the IP config file
        $bIPSetting = SaveIPSetting $Setting "Vlan" $EnableDHCP.CurrentValue $bSaveIPv6Settings

        if ($bIPSetting -eq $false)
        {
            WriteObjectToConfigFile $Setting $colProperty
        }
    }

    WriteTextToConfigFile "VlanSettingsEnd"
    WriteTextToConfigFile ""

    WriteTextToIPFile "VlanIPSettingsEnd"
    WriteTextToIPFile ""
}

#Save NICPART settings
Function SaveNICPARTSettings
{
    Write-Log $MyInvocation.MyCommand
    $colProperty = "Name","PartitionNumber","Identifier","MinBWPercent","MaxBWPercent"

    WriteTextToConfigFile "NICPARTSettingsStart"

    #start lock
    BeginApply

    try
    {
        #Get the partition information for all partitions on system and save them
        $PartitionArray = InvokeMethod $Script:service GetPartitionsForPort "szDeviceID" $null
            WriteObjectToConfigFile $PartitionArray.Partitions $colProperty
    }
    catch
    {
        Write-log "Unable to Save NIC Partition Settings"
    }

    #release lock
    ReleaseLock

    WriteTextToConfigFile "NICPARTSettingsEnd"
    WriteTextToConfigFile ""
}

#Remove teams and vlans on system
Function RemoveTeamsAndVlans
{
    Write-Log $MyInvocation.MyCommand

    $IANetObjs = @('IANet_TeamOfAdapters','IANet_Vlan')

    foreach ($IANetObj in $IANetObjs)
    {
        $colDevices = @()
        $colDevices += GetObject $IANetObj

        BeginApply

        foreach ($Device in $colDevices)
        {
            try
            {
                DeleteObject $Device
            }
            catch
            {
                Write-Log "Unable to DeleteObject `$Device $Device"
            }
        }

        ReleaseLock
    }
}

# Check that adapters in configuration file are present and enabled on the system.
Function CheckAdaptersExist
{
    Write-Log $MyInvocation.MyCommand
    $Script:colImportAdapters  = ReadFromConfigFile "Adapters"
    if ($null -ne $Script:colImportAdapters )
    {
        foreach ($Adapter in $Script:colImportAdapters)
        {
            # Find the matching file adapter using the appropriate property to compare name,address or ID
            if ($Script:BDF -eq $false)
            {
                if ($bUpRestore -eq $true)
                {
                    $ImportedAdapter = $Script:EnabledSystemAdapters | Where-Object {($_.PermanentAddress -eq $Adapter.PermanentAddress)}
                }
                else
                {
                    $ImportedAdapter = $Script:EnabledSystemAdapters | Where-Object {($_.PCIDeviceID -eq $Adapter.PCIDeviceID)}
                }

                if ($null -eq $ImportedAdapter)
                {
                    $ImportedAdapter = $Script:EnabledSystemAdapters | Where-Object {($_.Name -eq $Adapter.Name)}
                }
            }
            else
            {
                $ImportedAdapter = $Script:EnabledSystemAdapters | Where-Object {($_.SlotID -eq $Adapter.SlotID)}
            }


            if ($null -eq $ImportedAdapter)
            {
                Write-Warning UnableToFindDevice
                Write-Log "Invalid `$Adapter = $Adapter"
            }
        }
    }
}

# Restore custome OEM value
Function RestoreOEMCustomizeableSetting
{
    Write-Log $MyInvocation.MyCommand
    $ImportOEMSetting = ReadFromConfigFile "OEMSetting"
    if ($null -ne $ImportOEMSetting)
    {
        Write-Host $Messages.SettingOEMValue
        Set-ItemProperty -Path HKLM:\SOFTWARE\Intel\Network_Services\NCS2 -Name OEMCustomizeable -Value $ImportOEMSetting.OEMCustomizeable -Type DWord
    }
}

Function RestoreSettings($Setting, $stringClass, $enumClass)
{
    if ($Setting)
    {
        # IPv6 Address and DefaultGateway must be set with netsh, currently no API to set setting in Middleware
        if ($Setting.Caption -eq "IPv6Address")
        {
            # netsh interface ipv6 add address $ConnectionName $Address
            $ConnectionName = Get-WmiObject $stringClass -Namespace "root/intelncs2" | Where-Object {($_.Name -eq $Setting.Name) -and ($_.Caption -eq "ConnectionName")} | Select-Object -ExpandProperty CurrentValue
            $ConnectionInterfaceIndex = Get-WmiObject Win32_NetworkAdapter -Namespace "root/cimv2" | Where-Object {($_.NetConnectionID -eq $ConnectionName)} | Select-Object -ExpandProperty InterfaceIndex


            # delete current IPv6 addresses to overwrite them with saved config, if there are no addresses then we catch the exception and continue since there is nothing to delete.
            try
            {
                $AllAddresses = Get-WmiObject Win32_NetworkAdapterConfiguration -Namespace "root/cimv2" | Where-Object {($_.InterfaceIndex -eq $ConnectionInterfaceIndex)} | Select-Object -ExpandProperty IPAddress -ErrorAction SilentlyContinue
            }
            catch
            {
                Write-Log "No IPv6Address found."
            }

            $IPv6Array = @()
            foreach ($Address in $AllAddresses)
            {
                if (-not $Address.Contains("."))
                {
                    $IPv6Array += $Address
                }
            }

            #wrap interfaceindex in quotes for netsh
            $ConnectionInterfaceIndex = "`"$($ConnectionInterfaceIndex)`""

            foreach ($IP in $IPv6Array)
            {
                $DeleteCommand = "netsh interface ipv6 delete address $ConnectionInterfaceIndex $IP"
                Invoke-Expression $DeleteCommand
            }

            foreach ($Value in $Setting.CurrentValues) #CurrentValues is a list of IPv6Addresses split by comma, set each address
            {
                $Command = "netsh interface ipv6 add address $ConnectionInterfaceIndex $Value"
                Invoke-Expression $Command
            }
        }

        elseif ($Setting.Caption -eq "DefaultGatewayIPv6")
        {
            # netsh interface ipv6 add route ::/0 $ConnectionName $Address
            $ConnectionName = Get-WmiObject $stringClass -Namespace "root/intelncs2" | Where-Object {($_.Name -eq $Setting.Name) -and ($_.Caption -eq "ConnectionName")} | Select-Object -ExpandProperty CurrentValue
            $ConnectionInterfaceIndex = Get-WmiObject Win32_NetworkAdapter -Namespace "root/cimv2" | Where-Object {($_.NetConnectionID -eq $ConnectionName)} | Select-Object -ExpandProperty InterfaceIndex

            # delete current IPv6 gateways to overwrite them with saved config, if there are no gateways then we catch the exception and continue since there is nothing to delete.
            try
            {
                $AllGateways = Get-WmiObject Win32_NetworkAdapterConfiguration -Namespace "root/cimv2" | Where-Object {($_.InterfaceIndex -eq $ConnectionInterfaceIndex)} | Select-Object -ExpandProperty DefaultIPGateway -ErrorAction SilentlyContinue
            }
            catch
            {
                Write-Log "No DefaultGatewayIPv6 found."
            }

            $IPv6Array = @()
            foreach ($Address in $AllGateways)
            {
                if (-not $Address.Contains("."))
                {
                    $IPv6Array += $Address
                }
            }

            #wrap interfaceindex in quotes for netsh
            $ConnectionInterfaceIndex = "`"$($ConnectionInterfaceIndex)`""

            foreach ($IP in $IPv6Array)
            {
                $DeleteCommand = "netsh interface ipv6 delete route ::/0 $ConnectionInterfaceIndex $IP"
                Invoke-Expression $DeleteCommand
            }

            foreach ($Value in $Setting.CurrentValues)
            {
                $Command = "netsh interface ipv6 add route ::/0 $ConnectionInterfaceIndex $Value"
                Invoke-Expression $Command
            }
        }

        elseif ($Setting.Caption -eq "EnableDHCPv6")
        {
            $EnableDHCPv6Setting = Get-WmiObject $enumClass -Namespace "root/intelncs2" | Where-Object {($_.Name -eq $Setting.Name) -and ($_.Caption -eq "EnableDHCPv6")} | Select-Object -ExpandProperty CurrentValue

            if (($Setting.CurrentValue -eq "0") -and ($EnableDHCPv6Setting.CurrentValue -eq "1"))
            {
                $ConnectionName = Get-WmiObject $stringClass -Namespace "root/intelncs2" | Where-Object {($_.Name -eq $Setting.Name) -and ($_.Caption -eq "ConnectionName")} | Select-Object -ExpandProperty CurrentValue
                $ConnectionInterfaceIndex = Get-WmiObject Win32_NetworkAdapter -Namespace "root/cimv2" | Where-Object {($_.NetConnectionID -eq $ConnectionName)} | Select-Object -ExpandProperty InterfaceIndex

                $ConnectionInterfaceIndex = "`"$($ConnectionInterfaceIndex)`""

                $dhcpv6routercmd = "netsh int ipv6 set int $ConnectionInterfaceIndex routerdiscovery=disabled managedaddress=disabled otherstateful=disabled"

                Invoke-Expression $dhcpv6routercmd
                Start-Sleep -s 1
            }

            elseif (($Setting.CurrentValue -eq "1") -and ($EnableDHCPv6Setting.CurrentValue -eq "0"))
            {
                $ConnectionName = Get-WmiObject $stringClass -Namespace "root/intelncs2" | Where-Object {($_.Name -eq $Setting.Name) -and ($_.Caption -eq "ConnectionName")} | Select-Object -ExpandProperty CurrentValue
                $ConnectionInterfaceIndex = Get-WmiObject Win32_NetworkAdapter -Namespace "root/cimv2" | Where-Object {($_.NetConnectionID -eq $ConnectionName)} | Select-Object -ExpandProperty InterfaceIndex

                $ConnectionInterfaceIndex = "`"$($ConnectionInterfaceIndex)`""

                $dhcpv6managedcmd = "netsh int ipv6 set int $ConnectionInterfaceIndex managedaddress=enabled"

                Invoke-Expression $dhcpv6managedcmd
                Start-Sleep -s 1
            }
        }
        else
        {
            $result = SetSetting $Setting
            Write-Log "SetSetting $result"
        }
    }
}

# Restore adapter settings
Function RestoreAdapterSettings($colImportedSettings)
{
    Write-Log $($MyInvocation.MyCommand)
    Write-Log "`$colImportedSettings = $colImportedSettings"
    # Get adapter settings on system
    $colSetting = GetObject "IANet_AdapterSetting" | Sort-Object -Property Name,Caption
    # Add parent identifiers to settings on system
    foreach ($cSetting in $colSetting)
    {
        AddParentIDs $cSetting
    }

    # start lock
    BeginApply

    # Loop through the passed in settings from the config file
    foreach ($ImportedSetting in $colImportedSettings) #for each setting in AdapterIPSettings
    {
        try
        {
            # Find the matching system setting using the appropriate property to compare name,address or ID
            if ($Script:BDF -eq $false)
            {
                # finds the setting with the same caption and device name
                if (($bUpRestore -eq $true))
                {
                    $Setting = $colSetting | Where-Object {($_.PermanentAddress -eq $ImportedSetting.PermanentAddress) -and ($_.Caption -eq $ImportedSetting.Caption)}
                }
                else
                {
                    $Setting = $colSetting | Where-Object {($_.PCIDeviceID -eq $ImportedSetting.PCIDeviceID) -and ($_.Caption -eq $ImportedSetting.Caption)}
                }

                if ($null -eq $Setting)
                {
                    $Setting = $colSetting | Where-Object {($_.Name -eq $ImportedSetting.Name) -and ($_.Caption -eq $ImportedSetting.Caption)}
                }
            }
            else
            {
                $Setting = $colSetting | Where-Object {($_.SlotID -eq $ImportedSetting.SlotID) -and ($_.Caption -eq $ImportedSetting.Caption)}
            }

            # If the setting in the file can't be found on the system, continue to the next setting
            if ($null -eq $Setting)
            {
                Write-Log "Not Restored: $ImportedSetting"
                continue
            }

            # See if 'CurrentValues' is a property over the system setting, if so set that value from the config file setting
            if (($Setting.Properties | Select-Object -ExpandProperty Name) -contains "CurrentValues")
            {
                # If no value was present from the file setting, then set it to empty
                if ($null -eq $ImportedSetting.CurrentValues)
                {
                    $Setting.CurrentValues = {}
                }
                else
                {
                    $Setting.CurrentValues = $ImportedSetting.CurrentValues.Split(",")
                }
            }
            else
            {
                $Setting.CurrentValue = $ImportedSetting.CurrentValue
            }

            RestoreSettings $Setting "IANet_AdapterSettingString" "IANet_AdapterSettingEnum"
        }
        catch
        {
            $ErrorMessagePart1 = [string]$Messages.UnableToSetPart1
            $ErrorMessagePart2 = [string]$Messages.UnableToSetPart2
            # Unable to set ... on ...
            Write-Log "$ErrorMessagePart1 $($Setting.Caption) $ErrorMessagePart2 $($Setting.Name)"
        }
    }

    ReleaseLock
}

# Set the primary and secondary adapters on the team
Function RestorePrimaryAndSecondary($Team)
{
    Write-Log $MyInvocation.MyCommand
    Write-Log "`$Team = $Team"
    Write-Host $Messages.RestoringTeamAdapters $Team.TeamName

    # Find the adapter objects from the configuration file that match the team primary and secondary adapter names
    $PrimaryImportAdapter = $Script:colImportAdapters | Where-Object {$_.OriginalDisplayName -eq $Team.PrimaryAdapter}
    $SecondaryImportAdapter = $Script:colImportAdapters | Where-Object {$_.OriginalDisplayName -eq $Team.SecondaryAdapter}

    # Find the matching system adapter using the appropriate property to compare name,address or ID
    if ($Script:BDF -eq $false)
    {
        if ($bUpRestore)
        {
            $PrimaryAdapter = $Script:EnabledSystemAdapters | Where-Object {($_.PermanentAddress -eq $PrimaryImportAdapter.PermanentAddress)}
        }
        else
        {
            $PrimaryAdapter = $Script:EnabledSystemAdapters | Where-Object {($_.PCIDeviceID -eq $PrimaryImportAdapter.PCIDeviceID)}
        }
    }
    else
    {
        $PrimaryAdapter = $Script:EnabledSystemAdapters | Where-Object {($_.SlotID -eq $PrimaryImportAdapter.SlotID)}
    }

    if ($Script:BDF -eq $false)
    {
        if ($bUpRestore -eq $true)
        {
            $SecondaryAdapter = $Script:EnabledSystemAdapters | Where-Object {($_.PermanentAddress -eq $SecondaryImportAdapter.PermanentAddress)}
        }
        else
        {
            $SecondaryAdapter = $Script:EnabledSystemAdapters | Where-Object {($_.PCIDeviceID -eq $SecondaryImportAdapter.PCIDeviceID)}
        }
    }
    else
    {
        $SecondaryAdapter = $Script:EnabledSystemAdapters | Where-Object {($_.SlotID -eq $SecondaryImportAdapter.SlotID)}
    }

    # Set the primary and secondary adapters on the team
    $Team = GetObject "IANet_TeamOfAdapters" | Where-Object {$_.TeamName -eq $Team.TeamName} | Sort-Object -Property TeamName
    $param = $Team.psbase.GetMethodParameters("ValidateAndSetAdapterPriority")
    $param.PrimaryAdapter = $PrimaryAdapter
    $param.SecondaryAdapter = $SecondaryAdapter
    $param.SetValuesOnNoError = 2
    $value = $Team.psbase.InvokeMethod("ValidateAndSetAdapterPriority", $param, $invokeoptions)
    Write-Log $Value
}

# Restore teams from configuration file
Function RestoreTeams
{
    Write-Log $MyInvocation.MyCommand
    # Get the team objects from the configuration file
    $colImportTeams = ReadFromConfigFile "Teams"

    if ($null -ne $colImportTeams)
    {
        BeginApply

        foreach ($Team in $colImportTeams)
        {
            Write-Host $Messages.Restoring $Team.TeamName

            try
            {
                # Create a new team object
                $classdef = NewObject root\intelncs2:IANet_TeamOfAdapters

                # fill in the CreateTeam's method parameters with the new object
                $param = $classdef.psbase.GetMethodParameters("CreateTeam2Ex")

                $TeamedAdapters = $Team.TeamMembers.split("|")
                $param.Adapters = @()

                $bCreateTeam = $true
                # Set the teammembers for creation
                foreach ($Adapter in $TeamedAdapters)
                {
                    $Adapter = $Adapter.Split(",")
                    $AdapterName = $Adapter[0]
                    $AdapterPermanentAddress = $Adapter[1]
                    $AdapterPCIDeviceID = $Adapter[2]
                    $AdapterSlotID = $Adapter[3]
                    $Adapter = $null

                    # Find the matching system adapter using the appropriate property to compare address or ID
                    if ($Script:BDF -eq $false)
                    {
                        if ($bUpRestore -eq $true)
                        {
                            $Adapter = $Script:EnabledSystemAdapters | Where-Object {($_.PermanentAddress -eq $AdapterPermanentAddress)}
                        }
                        else
                        {
                            $Adapter = $Script:EnabledSystemAdapters | Where-Object {($_.PCIDeviceID -eq $AdapterPCIDeviceID)}
                        }

                        # Older versions of SaveRestore.ps1 won't have the Adapter's PermanentAddress, PCIDeviceID, or SlotID saved, so use the name.
                        if ($null -eq $Adapter)
                        {
                            $Adapter = $Script:EnabledSystemAdapters | Where-Object {($_.Name -eq $AdapterName)}
                        }
                    }
                    else
                    {
                        $Adapter = $Script:EnabledSystemAdapters | Where-Object {($_.SlotID -eq $AdapterSlotID)}
                    }

                    # If a teammember can't be found output an error and breakout of team creation
                    if ($null -eq $Adapter)
                    {
                        Write-Warning UnableToFindDevice
                        Write-Log $AdapterName
                        $bCreateTeam = $false
                        break
                    }
                    $param.Adapters += $Adapter
                }

                # If an error was found, don't try and create the team
                if ($bCreateTeam -eq $false)
                {
                    Continue
                }

                $param.TeamMode = $Team.TeamMode
                $param.TeamName = $Team.TeamName

                # the invoke options
                $invokeoptions = New-Object System.Management.InvokeMethodOptions
                $invokeoptions.Context = $Script:context

                # call the CreateTeam method to create the new team
                $null = $classdef.psbase.InvokeMethod("CreateTeam2Ex", $param, $invokeoptions)

                # Set primary and secondary adapters
                RestorePrimaryAndSecondary $Team
            }
            catch
            {
                Write-Log "Unable to Restore Teams"
            }
        }

        ReleaseLock
    }
}

#Restore team settings
Function RestoreTeamSettings($colImportSettings)
{
    Write-Log $MyInvocation.MyCommand
    Write-Log "`$colImportSettings = $colImportSettings"
    # Get the current team settings
    $colSetting = GetObject "IANet_TeamSetting" | Sort-Object -Property Name,Caption

    BeginApply

    foreach ($Setting in $colSetting)
    {
        try
        {
            # If the current setting using 'CurrentValues' then set that value.
            if (($Setting.Properties | Select-Object -ExpandProperty Name) -contains "CurrentValues")
            {
                $ValueObject = $colImportSettings | Where-Object {($_.TeamName -eq $Setting.Name) -and ($_.Caption -eq $Setting.Caption)} | Select-Object CurrentValues
                if ($null -eq $ValueObject.CurrentValues)
                {
                    $Setting.CurrentValues = {}
                }
                else
                {
                    $Setting.CurrentValues = $ValueObject.CurrentValues.Split(",")
                }
            }
            else
            {
                $ValueObject = $colImportSettings | Where-Object {($_.TeamName -eq $Setting.Name) -and ($_.Caption -eq $Setting.Caption)} | Select-Object CurrentValue
                $Setting.CurrentValue = $ValueObject.CurrentValue
            }

            #If a matching system setting was found set it to the restore value
            if($null -ne $ValueObject)
            {
                RestoreSettings $Setting "IANet_TeamSettingString" "IANet_TeamSettingEnum"
            }

        }
        catch
        {
            $ErrorMessagePart1 = [string]$Messages.UnableToSetPart1
            $ErrorMessagePart2 = [string]$Messages.UnableToSetPart2
            # Unable to set ... on ...
            Write-Log "$ErrorMessagePart1 $($Setting.Caption) $ErrorMessagePart2 $($Setting.Name)"
        }
    }

    ReleaseLock
}

Function RestoreVlans
{
    Write-Log $MyInvocation.MyCommand
    # Get vlan objects from config file
    $colImportVlans = ReadFromConfigFile "Vlans"

    if ($null -ne $colImportVlans)
    {
        foreach ($Vlan in $colImportVlans)
        {
            # Start lock
            BeginApply
            try
            {
                # Find the matching system adapter using the appropriate property to compare name,address or ID
                if ($Script:BDF -eq $false)
                {
                    if ($bUpRestore -eq $true)
                    {
                        $Adapter = $Script:Adapters | Where-Object {($_.PermanentAddress -eq $Vlan.ParentPermanentAddress)}
                    }
                    else
                    {
                        $Adapter = $Script:Adapters | Where-Object {($_.PCIDeviceID -eq $Vlan.ParentPCIDeviceID)}
                    }

                    # Older versions of SaveRestore.ps1 won't have the Adapter's PermanentAddress, PCIDeviceID, or SlotID saved, so use the name.
                    if ($null -eq $Adapter)
                    {
                        $Adapter = $Script:Adapters | Where-Object {($_.Name -eq $Vlan.ParentName)}
                    }
                }
                else
                {
                    $Adapter = $Script:Adapters | Where-Object {($_.SlotID -eq $Vlan.ParentSlotID)}
                }

                if ($null -ne $Adapter)
                {
                    # Get the vlan creation class associated with adapter parent object
                    $VlanCreator = GetAssociated $Adapter.path.path "ResultClass = IANet_802dot1QVLANService" | Where-Object {1}
                }
                # Check if vlan needs to be created on a team
                else
                {
                    # Get the logical ethernet adapter object for the team parent
                    $Team = GetObject "IANet_LogicalEthernetAdapter" | Where-Object {( NScompare $_.Name $Vlan.ParentName )}

                    if ($null -ne $Team)
                    {
                        # Get the vlan creation class associated with team parent object
                        $VlanCreator = GetAssociated $Team.path.path "ResultClass = IANet_802dot1QVLANService" | Where-Object {1}
                    }
                }

                # If the vlan creation class was found continue on to create the vlan
                if ($null -ne $VlanCreator)
                {
                    Write-Host $Messages.Restoring $Vlan.VlanName

                    # fill in the CreateVlan's method parameters
                    $param = $VlanCreator.psbase.GetMethodParameters("CreateVlan")

                    $param.Name = $Vlan.VLANNAME
                    $param.VLANNumber = $Vlan.VLANID

                    # the invoke options
                    $invokeoptions = New-Object System.Management.InvokeMethodOptions
                    $invokeoptions.Context = $Script:context

                    # call the CreateVlan method to create the new vlan
                    $null = $VlanCreator.psbase.InvokeMethod("CreateVlan", $param, $invokeoptions)
                }
            }
            catch
            {
                Write-Log "Unable to Restore VLANs"
            }
            ReleaseLock
        }
    }
}

# Restore vlan settings
Function RestoreVlanSettings($colImportSettings)
{
    Write-Log $MyInvocation.MyCommand
    Write-Log "`$colImportSettings = $colImportSettings"
    # Get the current Vlan settings
    $vlanSettings = GetObject "IANet_VLANSetting" | Sort-Object -Property ParentName,VLANID,Caption

    # Start lock
    BeginApply

    foreach ($Setting in $vlanSettings)
    {
        try
        {
            if ($Script:BDF -eq $false)
            {
                #finds the setting with the same caption and device name
                if ($bUpRestore -eq $true)
                {
                    $ValueObject = $colImportSettings | Where-Object {($_.ParentPermanentAddress -eq $Setting.ParentPermanentAddress) -and ($_.Caption -eq $Setting.Caption) -and ($_.VLANID -eq $Setting.VLANID)}
                }
                else
                {
                    $ValueObject = $colImportSettings | Where-Object {($_.ParentPCIDeviceID -eq $Setting.ParentPCIDeviceID) -and ($_.Caption -eq $Setting.Caption) -and ($_.VLANID -eq $Setting.VLANID)}
                }

                # Older versions of SaveRestore.ps1 won't have the Adapter's PermanentAddress, PCIDeviceID, or SlotID saved, so use the name.
                if ($null -eq $ValueObject)
                {
                    $ValueObject = $colImportSettings | Where-Object {( NSCompare $_.ParentName $Setting.ParentName ) -and ( $_.Caption -eq $Setting.Caption) -and ($_.VLANID -eq $Setting.VLANID)}
                }
            }
            else
            {
                $ValueObject = $colImportSettings | Where-Object {($_.ParentSlotID -eq $Setting.ParentSlotID) -and ($_.Caption -eq $Setting.Caption) -and ($_.VLANID -eq $Setting.VLANID)}
            }

            if (($Setting.Properties | Select-Object -ExpandProperty Name) -contains "CurrentValues")
            {
                if ($null -eq $ValueObject.CurrentValues)
                {
                    $Setting.CurrentValues = {}
                }
                else
                {
                    $Setting.CurrentValues = $ValueObject.CurrentValues.Split(",")
                }
            }
            else
            {
                $Setting.CurrentValue = $ValueObject.CurrentValue
            }

            # If a matching system setting was found set it to the restore value
            if($null -ne $ValueObject)
            {
                RestoreSettings $Setting "IANet_VLANSettingString" "IANet_VLANSettingEnum"
            }

        }
        catch
        {
            $ErrorMessagePart1 = [string]$Messages.UnableToSetPart1
            $ErrorMessagePart2 = [string]$Messages.UnableToSetPart2
            # Unable to set ... on ...
            Write-Log "$ErrorMessagePart1 $($Setting.Caption) $ErrorMessagePart2 $($Setting.Name)"
        }
    }

    ReleaseLock
}

# Restore the IP settings from the IP config file
Function RestoreIPSettings
{
    Write-Log $MyInvocation.MyCommand
    # Restore Adapter IP settings from the IP config file
    $colImportSettings = ReadFromIPFile "AdapterIPSettings"

    if ($null -ne $colImportSettings)
    {
        foreach ($setting in $colImportSettings)
        {
            # If the staticip is set on a disconnected adapter from cmdline tools (netsh, New-NetIPAddress, Set-NetIPInterface, etc)
            # the private 169.254.x.x IP that Microsoft assigns when it can't get an address from the DHCP server
            # comes along and saverestore ends up restoring that IP with a 255.255.0.0 subnetmask instead.
            # Only capture the first ip, which is what static ip and subnetmask the user intended to set
            # ignore the 169.254.x.x IP and 255.255.0.0 subnetmask
            if (($setting.Caption -eq 'IPv4Address') -or
                ($setting.Caption -eq 'SubnetMask'))
            {
                if (($setting.CurrentValues.ToCharArray()) -contains [char]',')
                {
                    $setting.CurrentValues = $setting.CurrentValues.Substring(0, $setting.CurrentValues.lastIndexOf(','))
                }
            }
        }

        Write-Host $Messages.RestoringAdapterIP
        RestoreAdapterSettings $colImportSettings
    }

    if ($Script:bANSInstalled)
    {
        # Restore Team IP settings from the IP config file
        $colImportSettings = ReadFromIPFile "TeamIPSettings"
        if ($null -ne $colImportSettings)
        {
            Write-Host $Messages.RestoringTeamIP
            RestoreTeamSettings $colImportSettings $colSetting
        }

        # Restore Vlan IP settings from the IP config file
        $colImportSettings = ReadFromIPFile "VlanIPSettings"
        if ($null -ne $colImportSettings)
        {
            Write-Host $Messages.RestoringVLANIP
            RestoreVlanSettings $colImportSettings
        }
    }
}

# Restore devices and settings from configuration files
Function RestoreAdvancedSettingsAndDevices
{
    Write-Log $MyInvocation.MyCommand
    #Restore Adapter avanced settings from config file
    $colImportSettings = ReadFromConfigFile "AdapterSettings"
    if ($null -ne $colImportSettings)
    {
        Write-Host $Messages.RestoringAdapterSettings
        RestoreAdapterSettings $colImportSettings
    }

    if (!$Script:bANSInstalled)
    {
        Write-Warning UnableToFindANS
    }
    else
    {
        #Restore Teams
        RestoreTeams

        #Restore Team advanced settings from config file
        $colImportSettings = ReadFromConfigFile "TeamSettings"
        if ($null -ne $colImportSettings)
        {
            Write-Host $Messages.RestoringTeamSettings
            RestoreTeamSettings $colImportSettings
        }

        #Restore Vlans
        RestoreVlans

        #Restore Vlan avanced settings from config file
        $colImportSettings = ReadFromConfigFile "VlanSettings"
        if ($null -ne $colImportSettings)
        {
            Write-Host $Messages.RestoringVLANSettings
            RestoreVlanSettings $colImportSettings
        }
    }

    if (Test-Path $Script:ScriptPath\Saved_StaticIP.txt)
    {
        #Restore IP Settings from IP config file
        RestoreIPSettings
    }
}

#Restore NICPART settings
Function RestoreNICPARTSettings
{
    Write-Log $MyInvocation.MyCommand
    #Restore NICPART settings from config file
    $colImportPartitions = ReadFromConfigFile "NICPARTSettings"
    Write-Host $Messages.RestoringAdapterPartition

    BeginApply

    try
    {
        if ($null -ne $colImportPartitions)
        {
            #Get the partition information for all partitions on system and save them
            $PartitionObject = InvokeMethod $Script:service GetPartitionsForPort "szDeviceID" $null
            $PartitionArray = $PartitionObject.Partitions


            foreach ($Partition in $PartitionArray)
            {
                $ImportPartition = $colImportPartitions | Where-Object {($_.PartitionNumber -eq $Partition.PartitionNumber) -and ($_.Identifier -eq $Partition.Identifier)}

                if ($null -ne $ImportPartition)
                {
                    if ($Script:BDF -eq $true)
                    {
                        $Partition.Name = "IGNORE"
                        $Partition.DeviceGuid = "IGNORE"
                    }
                    else
                    {
                        $Partition.Identifier = "IGNORE"
                        $Partition.DeviceGuid = "IGNORE"
                    }

                    $Partition.MinBWPercent = $ImportPartition.MinBWPercent
                    $Partition.MaxBWPercent = $ImportPartition.MaxBWPercent
                }
            }

            $Output = InvokeMethod $Script:service ValidateAndSetBAndwidthsForPort "Partitions","SetValuesOnNoError" $PartitionArray,"1"
        }
    }
    catch
    {
        Write-Log "Unable to Restore NIC Partition Settings"
    }

    ReleaseLock
}


<#
.SYNOPSIS
    Make sure users know about BDF parameter when using NICPART enabled adapters
.DESCRIPTION
    By default, the save restore script uses the adapter's friendly name to match the name of the adapter
    in the configuration file to adapter in the system.  Because of the way Windows enumerates devices and
    assigns friendly names, the name of the adapter saved in the configuration file may not be the exact
    same adapter/port/partition on another system. The /bdf command line option should be used when
    restoring settings of NICPART enabled devices on multiple systems.
.NOTES
    This must be invoked after GetAdaptersOnSystem - as it relies on the contents of $Script:Adapters.
    The global variable is used to save time when restoring settings on user systems (to avoid polling
    the system again just to get the same data).
#>
function CheckNICPART_BDFWarning
{
    Write-Log $MyInvocation.MyCommand
    $NICPART = $Script:Adapters | Where-Object {($_.Capabilities -eq 76)}
    if ($NICPART -and $Script:BDF -eq $false)
    {
        Write-Host $Messages.NICPartitioningDetected
    }

}

<#
.SYNOPSIS
    "No Space Compare" Compares two strings without whitespace
.DESCRIPTION
    Intel(R) Network Connections Software recently introduced a change to ANS team prefixes. The old format
    was "TEAM : ". The new format is "TEAM: ". To preserve user configurations during an upgrade it is
    necessary to compare some devices without space differences.
#>
function NSCompare ($s1, $s2)
{
    Write-Log $MyInvocation.MyCommand
    Write-Log "Comparing `$s1=$s1 and `$s2=$s2..."
    $s1temp = $s1 -replace '\s', ''
    $s2temp = $s2 -replace '\s', ''
    return ($s1temp -eq $s2temp)
}

#-----------------------------------------------------[Execution]------------------------------------------------------

# Determine which action was specified by the user
switch($Script:Action)
{
    "Save" {$Script:bSave=$true
            Write-Host $Messages.PerformingSave
            break}
    "UpSave" {$Script:bUpSave=$true
            Write-Host $Messages.PerformingUpgradeSave
            break}
    "Restore" {$Script:bRestore=$true
            Write-Host $Messages.PerformingRestore
            break}
    "UpRestore" {$Script:bUpRestore=$true
            Write-Host $Messages.PerformingUpgradeRestore
            break}
    "Remove" {$Script:bRemove=$true
            Write-Host $Messages.PerformingRemove
            break}
    "?" {PrintUsage}
    "help"{PrintUsage}
    default {PrintUsage}
}

CheckForAdminRights
CheckForDMiXInstall
CheckForANSInstall

# Setup the save/restore path
SetupSaveRestoreLocation

# If a save is the primary action
if (($Script:bSave -eq $true) -or ($Script:bUpSave -eq $true))
{
    GetAdaptersOnSystem

    RemoveOldFiles

    SaveAdapters

    SaveOEMCustomizeableSetting

    SaveAdapterSettings

    if ($Script:bANSInstalled)
    {
        SaveTeams

        SaveTeamSettings

        SaveVlans

        SaveVlanSettings
    }

    SaveNICPARTSettings
}
# If a restore is the primary action
elseif (($Script:bRestore -eq $true) -or ($Script:bUpRestore -eq $true))
{
    CheckIfConfigFileExists

    if ($Script:bANSInstalled)
    {
        RemoveTeamsAndVlans
    }

    GetAdaptersOnSystem

    CheckNICPART_BDFWarning

    CheckAdaptersExist

    RestoreOEMCustomizeableSetting

    RestoreAdvancedSettingsAndDevices

    RestoreNICPARTSettings
}
# If remove is the primary action
elseif ($Script:bRemove -eq $true)
{
    RemoveTeamsAndVlans
}


# SIG # Begin signature block
# MIIt/wYJKoZIhvcNAQcCoIIt8DCCLewCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCJ56012BpHMdrB
# Qd6pnUwCPBRTFqW0eYkFeXq0kfXsTaCCEfcwggVvMIIEV6ADAgECAhBI/JO0YFWU
# jTanyYqJ1pQWMA0GCSqGSIb3DQEBDAUAMHsxCzAJBgNVBAYTAkdCMRswGQYDVQQI
# DBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoM
# EUNvbW9kbyBDQSBMaW1pdGVkMSEwHwYDVQQDDBhBQUEgQ2VydGlmaWNhdGUgU2Vy
# dmljZXMwHhcNMjEwNTI1MDAwMDAwWhcNMjgxMjMxMjM1OTU5WjBWMQswCQYDVQQG
# EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS0wKwYDVQQDEyRTZWN0aWdv
# IFB1YmxpYyBDb2RlIFNpZ25pbmcgUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQCN55QSIgQkdC7/FiMCkoq2rjaFrEfUI5ErPtx94jGgUW+s
# hJHjUoq14pbe0IdjJImK/+8Skzt9u7aKvb0Ffyeba2XTpQxpsbxJOZrxbW6q5KCD
# J9qaDStQ6Utbs7hkNqR+Sj2pcaths3OzPAsM79szV+W+NDfjlxtd/R8SPYIDdub7
# P2bSlDFp+m2zNKzBenjcklDyZMeqLQSrw2rq4C+np9xu1+j/2iGrQL+57g2extme
# me/G3h+pDHazJyCh1rr9gOcB0u/rgimVcI3/uxXP/tEPNqIuTzKQdEZrRzUTdwUz
# T2MuuC3hv2WnBGsY2HH6zAjybYmZELGt2z4s5KoYsMYHAXVn3m3pY2MeNn9pib6q
# RT5uWl+PoVvLnTCGMOgDs0DGDQ84zWeoU4j6uDBl+m/H5x2xg3RpPqzEaDux5mcz
# mrYI4IAFSEDu9oJkRqj1c7AGlfJsZZ+/VVscnFcax3hGfHCqlBuCF6yH6bbJDoEc
# QNYWFyn8XJwYK+pF9e+91WdPKF4F7pBMeufG9ND8+s0+MkYTIDaKBOq3qgdGnA2T
# OglmmVhcKaO5DKYwODzQRjY1fJy67sPV+Qp2+n4FG0DKkjXp1XrRtX8ArqmQqsV/
# AZwQsRb8zG4Y3G9i/qZQp7h7uJ0VP/4gDHXIIloTlRmQAOka1cKG8eOO7F/05QID
# AQABo4IBEjCCAQ4wHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQwHQYD
# VR0OBBYEFDLrkpr/NZZILyhAQnAgNpFcF4XmMA4GA1UdDwEB/wQEAwIBhjAPBgNV
# HRMBAf8EBTADAQH/MBMGA1UdJQQMMAoGCCsGAQUFBwMDMBsGA1UdIAQUMBIwBgYE
# VR0gADAIBgZngQwBBAEwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21v
# ZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEE
# KDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZI
# hvcNAQEMBQADggEBABK/oe+LdJqYRLhpRrWrJAoMpIpnuDqBv0WKfVIHqI0fTiGF
# OaNrXi0ghr8QuK55O1PNtPvYRL4G2VxjZ9RAFodEhnIq1jIV9RKDwvnhXRFAZ/ZC
# J3LFI+ICOBpMIOLbAffNRk8monxmwFE2tokCVMf8WPtsAO7+mKYulaEMUykfb9gZ
# pk+e96wJ6l2CxouvgKe9gUhShDHaMuwV5KZMPWw5c9QLhTkg4IUaaOGnSDip0TYl
# d8GNGRbFiExmfS9jzpjoad+sPKhdnckcW67Y8y90z7h+9teDnRGWYpquRRPaf9xH
# +9/DUp/mBlXpnYzyOmJRvOwkDynUWICE5EV7WtgwggYcMIIEBKADAgECAhAz1wio
# kUBTGeKlu9M5ua1uMA0GCSqGSIb3DQEBDAUAMFYxCzAJBgNVBAYTAkdCMRgwFgYD
# VQQKEw9TZWN0aWdvIExpbWl0ZWQxLTArBgNVBAMTJFNlY3RpZ28gUHVibGljIENv
# ZGUgU2lnbmluZyBSb290IFI0NjAeFw0yMTAzMjIwMDAwMDBaFw0zNjAzMjEyMzU5
# NTlaMFcxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxLjAs
# BgNVBAMTJVNlY3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBDQSBFViBSMzYwggGi
# MA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQC70f4et0JbePWQp64sg/GNIdMw
# hoV739PN2RZLrIXFuwHP4owoEXIEdiyBxasSekBKxRDogRQ5G19PB/YwMDB/NSXl
# wHM9QAmU6Kj46zkLVdW2DIseJ/jePiLBv+9l7nPuZd0o3bsffZsyf7eZVReqskmo
# PBBqOsMhspmoQ9c7gqgZYbU+alpduLyeE9AKnvVbj2k4aOqlH1vKI+4L7bzQHkND
# brBTjMJzKkQxbr6PuMYC9ruCBBV5DFIg6JgncWHvL+T4AvszWbX0w1Xn3/YIIq62
# 0QlZ7AGfc4m3Q0/V8tm9VlkJ3bcX9sR0gLqHRqwG29sEDdVOuu6MCTQZlRvmcBME
# Jd+PuNeEM4xspgzraLqVT3xE6NRpjSV5wyHxNXf4T7YSVZXQVugYAtXueciGoWnx
# G06UE2oHYvDQa5mll1CeHDOhHu5hiwVoHI717iaQg9b+cYWnmvINFD42tRKtd3V6
# zOdGNmqQU8vGlHHeBzoh+dYyZ+CcblSGoGSgg8sCAwEAAaOCAWMwggFfMB8GA1Ud
# IwQYMBaAFDLrkpr/NZZILyhAQnAgNpFcF4XmMB0GA1UdDgQWBBSBMpJBKyjNRsjE
# osYqORLsSKk/FDAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAT
# BgNVHSUEDDAKBggrBgEFBQcDAzAaBgNVHSAEEzARMAYGBFUdIAAwBwYFZ4EMAQMw
# SwYDVR0fBEQwQjBAoD6gPIY6aHR0cDovL2NybC5zZWN0aWdvLmNvbS9TZWN0aWdv
# UHVibGljQ29kZVNpZ25pbmdSb290UjQ2LmNybDB7BggrBgEFBQcBAQRvMG0wRgYI
# KwYBBQUHMAKGOmh0dHA6Ly9jcnQuc2VjdGlnby5jb20vU2VjdGlnb1B1YmxpY0Nv
# ZGVTaWduaW5nUm9vdFI0Ni5wN2MwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNl
# Y3RpZ28uY29tMA0GCSqGSIb3DQEBDAUAA4ICAQBfNqz7+fZyWhS38Asd3tj9lwHS
# /QHumS2G6Pa38Dn/1oFKWqdCSgotFZ3mlP3FaUqy10vxFhJM9r6QZmWLLXTUqwj3
# ahEDCHd8vmnhsNufJIkD1t5cpOCy1rTP4zjVuW3MJ9bOZBHoEHJ20/ng6SyJ6UnT
# s5eWBgrh9grIQZqRXYHYNneYyoBBl6j4kT9jn6rNVFRLgOr1F2bTlHH9nv1HMePp
# GoYd074g0j+xUl+yk72MlQmYco+VAfSYQ6VK+xQmqp02v3Kw/Ny9hA3s7TSoXpUr
# OBZjBXXZ9jEuFWvilLIq0nQ1tZiao/74Ky+2F0snbFrmuXZe2obdq2TWauqDGIgb
# MYL1iLOUJcAhLwhpAuNMu0wqETDrgXkG4UGVKtQg9guT5Hx2DJ0dJmtfhAH2KpnN
# r97H8OQYok6bLyoMZqaSdSa+2UA1E2+upjcaeuitHFFjBypWBmztfhj24+xkc6Zt
# CDaLrw+ZrnVrFyvCTWrDUUZBVumPwo3/E3Gb2u2e05+r5UWmEsUUWlJBl6MGAAjF
# 5hzqJ4I8O9vmRsTvLQA1E802fZ3lqicIBczOwDYOSxlP0GOabb/FKVMxItt1UHeG
# 0PL4au5rBhs+hSMrl8h+eplBDN1Yfw6owxI9OjWb4J0sjBeBVESoeh2YnZZ/WVim
# VGX/UUIL+Efrz/jlvzCCBmAwggTIoAMCAQICEQDG1BY1LpZAzz0nTI0UAhb4MA0G
# CSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExp
# bWl0ZWQxLjAsBgNVBAMTJVNlY3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBDQSBF
# ViBSMzYwHhcNMjUwMjE3MDAwMDAwWhcNMjYwMjE3MjM1OTU5WjCBvTEQMA4GA1UE
# BRMHMTYzNjAzMjETMBEGCysGAQQBgjc8AgEDEwJVUzEbMBkGCysGAQQBgjc8AgEC
# EwpDYWxpZm9ybmlhMR0wGwYDVQQPExRQcml2YXRlIE9yZ2FuaXphdGlvbjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExGjAYBgNVBAoMEUludGVsIENv
# cnBvcmF0aW9uMRowGAYDVQQDDBFJbnRlbCBDb3Jwb3JhdGlvbjCCAaIwDQYJKoZI
# hvcNAQEBBQADggGPADCCAYoCggGBAOSJwDzSkOYLkY/IN/p/2nFeLNo40BOlLEWQ
# 6pag3EaAFNyqy9N8Eg2fureZMV2iTR8Uqd4LbKi1j1NX3M4mNNqmhB2/ILTyLeR5
# z2QhqmixkDQRJy6pCdGy3Mh8JQq5GXmc3v7WM6BG57F87hcZU+LfQu/4Hpn9uYmc
# mhCbX/fn1UmOgkslt/Ih1L0akM+4IuoNzzw288r070PiRD8ivJBBTX9ql1Y3zeiY
# r/qDaBY5d/UrlC3Np2xBqxzslYlr+MNLkEBO3oLoU3LqaBTaIQb0pFUVCYTDP9Ax
# 79nagpeB1Kq5IHJZ2UTOpnoeRJAbWhWkv9/qJ//24kaeWcI5U24P7ou8I/oyuGna
# ZPelZwZ2finYwHInTI1M2HK4UB4ZK47eWV88RrEzUznroB+tWMtdKN5eoidalMLm
# xtKcaFAhwkMliofrTZzKp2+kVqTkldNSiBnRZgcdkJChcYSu9uYnzN4b/0AzljPu
# OEgTY5bwTKMO+Y7AsYr0Y/s/4CaMvQIDAQABo4IBvjCCAbowHwYDVR0jBBgwFoAU
# gTKSQSsozUbIxKLGKjkS7EipPxQwHQYDVR0OBBYEFCcXWdnyVsj83/x0QM2NgaLP
# +tD2MA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG
# AQUFBwMDMEkGA1UdIARCMEAwNQYMKwYBBAGyMQECAQYBMCUwIwYIKwYBBQUHAgEW
# F2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMAcGBWeBDAEDMEsGA1UdHwREMEIwQKA+
# oDyGOmh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2VjdGlnb1B1YmxpY0NvZGVTaWdu
# aW5nQ0FFVlIzNi5jcmwwewYIKwYBBQUHAQEEbzBtMEYGCCsGAQUFBzAChjpodHRw
# Oi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNDb2RlU2lnbmluZ0NBRVZS
# MzYuY3J0MCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTAwBgNV
# HREEKTAnoCUGCCsGAQUFBwgDoBkwFwwVVVMtQ0FMSUZPUk5JQS0xNjM2MDMyMA0G
# CSqGSIb3DQEBCwUAA4IBgQB1LvgC6QisrQ4m1nFgTbEO6ifHNefj+4pqH0DgDdn/
# NFqZKuEwQz7sOM36lmQAtyCGZb6UJLAH4LnJMCQ43vbQrKq98q7P5Yu02Bsk/MCB
# pQuD2lKxnBuQYEmnWSK7lR4lNcP9zVUKJ9PuW6i9d51y/e/tx9dTF14uYbgv68QO
# b41cFEm2qkulhwy8Q3VFp3kyny829LlXcAv57DMnKfnap0AhhOqY5UY5d8vPP1+I
# PB+4ijD4wElt5k7oBIWFRm2kkeYtEor/pNVG5JCm61X+9BogIhxweCVoVvHcdBPR
# FSlThifJ2r/mHVkMXta73oaOLrw74mxpqckt19bgKhM8Pwct4b5LR/oOvPS6xlBo
# 4XJucZzcvQ/yvCYBVozDSktdtBm3o79/jEv6VQ88Kc7uBMGK59VdSA8U2NGzoqjd
# KKa94pF9AePgRaTholV4MFPISXCJUG8o7AoMTU89oebrs1epIeYqYfIBnVbyHEQU
# 6AchfmgVhzxqCXPjb6pWeNgxghteMIIbWgIBATBsMFcxCzAJBgNVBAYTAkdCMRgw
# FgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxLjAsBgNVBAMTJVNlY3RpZ28gUHVibGlj
# IENvZGUgU2lnbmluZyBDQSBFViBSMzYCEQDG1BY1LpZAzz0nTI0UAhb4MA0GCWCG
# SAFlAwQCAQUAoGowGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGC
# NwIBCzEOMAwGCisGAQQBgjcCARYwLwYJKoZIhvcNAQkEMSIEIDt9KnLFu/SuSThL
# IwhB8bTO/A3nycr00KBl7LHwchX8MA0GCSqGSIb3DQEBAQUABIIBgNSRtPmiikTy
# +JBvJNcKtlG78i+OYEZk/rbnJgkUwCGZFlGvTxrm1UCJaSvX+QpDfuCOxhl6Dk8V
# 5zQkANMNpv4Hr/lQimjS5axFjNjda8lZLIl8YNcRIZPlJWgTA1A3jy1A62e2Qmz3
# MW0V3ov0LmReSvd1BD55thGODNg/z/gpegVFplZGr1m+TbvhVRUU+FrZChmbrLng
# gYRYwHyGJ4jVUukrLfFlTQ7mlCr5IMUYkGzrn0eyg+QoFXYfnKYxZ73aWcYED0lB
# X8bVVJWxNQcQXF0LhvPZhw2lLzMsOoDOzzzJP0uH5LsvLK3323xSmSEqrBioIWvt
# eOIeiqzQHWxuECTlECYlceAhUaNtKlMSiatN1p7h8vzkfDDghl9eoVZbUglD0uN9
# /9U48cqfOa8wiXd+LlVRrBBy2lfcY7zfg4QldXLPbhLWL20T5HHrJXxwjEXx/OLY
# EnTagK5psmPdOF8+enM/H45x2N08g7qZ+M0jSuSx6GTR223z0/dgE6GCGNcwghjT
# BgorBgEEAYI3AwMBMYIYwzCCGL8GCSqGSIb3DQEHAqCCGLAwghisAgEDMQ8wDQYJ
# YIZIAWUDBAICBQAwgfcGCyqGSIb3DQEJEAEEoIHnBIHkMIHhAgEBBgorBgEEAbIx
# AgEBMDEwDQYJYIZIAWUDBAIBBQAEIE2n3HCuOk9NN0KrW4JQ+ydYggbNYEGd8mds
# FzgmE0gRAhRtnxG6BzTaJbF9IHyUQ5x3dbJ0ChgPMjAyNTA1MTYxNjM5NTNaoHak
# dDByMQswCQYDVQQGEwJHQjEXMBUGA1UECBMOV2VzdCBZb3Jrc2hpcmUxGDAWBgNV
# BAoTD1NlY3RpZ28gTGltaXRlZDEwMC4GA1UEAxMnU2VjdGlnbyBQdWJsaWMgVGlt
# ZSBTdGFtcGluZyBTaWduZXIgUjM2oIITBDCCBmIwggTKoAMCAQICEQCkKTtuHt3X
# pzQIh616TrckMA0GCSqGSIb3DQEBDAUAMFUxCzAJBgNVBAYTAkdCMRgwFgYDVQQK
# Ew9TZWN0aWdvIExpbWl0ZWQxLDAqBgNVBAMTI1NlY3RpZ28gUHVibGljIFRpbWUg
# U3RhbXBpbmcgQ0EgUjM2MB4XDTI1MDMyNzAwMDAwMFoXDTM2MDMyMTIzNTk1OVow
# cjELMAkGA1UEBhMCR0IxFzAVBgNVBAgTDldlc3QgWW9ya3NoaXJlMRgwFgYDVQQK
# Ew9TZWN0aWdvIExpbWl0ZWQxMDAuBgNVBAMTJ1NlY3RpZ28gUHVibGljIFRpbWUg
# U3RhbXBpbmcgU2lnbmVyIFIzNjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBANOElfRupFN48j0QS3gSBzzclIFTZ2Gsn7BjsmBF659/kpA2Ey7NXK3MP6Jd
# rMBNU8wdmkf+SSIyjX++UAYWtg3Y/uDRDyg8RxHeHRJ+0U1jHEyH5uPdk1ttiPC3
# x/gOxIc9P7Gn3OgW7DQc4x07exZ4DX4XyaGDq5LoEmk/BdCM1IelVMKB3WA6YpZ/
# XYdJ9JueOXeQObSQ/dohQCGyh0FhmwkDWKZaqQBWrBwZ++zqlt+z/QYTgEnZo6dy
# Io2IhXXANFkCHutL8765NBxvolXMFWY8/reTnFxk3MajgM5NX6wzWdWsPJxYRhLx
# tJLSUJJ5yWRNw+NBqH1ezvFs4GgJ2ZqFJ+Dwqbx9+rw+F2gBdgo4j7CVomP49sS7
# CbqsdybbiOGpB9DJhs5QVMpYV73TVV3IwLiBHBECrTgUfZVOMF0KSEq2zk/Lsfve
# hswavE3W4aBXJmGjgWSpcDz+6TqeTM8f1DIcgQPdz0IYgnT3yFTgiDbFGOFNt6eC
# idxdR6j9x+kpcN5RwApy4pRhE10YOV/xafBvKpRuWPjOPWRBlKdm53kS2aMh08sp
# x7xSEqXn4QQldCnUWRz3Lki+TgBlpwYwJUbR77DAayNwAANE7taBrz2v+MnnogMr
# vvct0iwvfIA1W8kp155Lo44SIfqGmrbJP6Mn+Udr3MR2oWozAgMBAAGjggGOMIIB
# ijAfBgNVHSMEGDAWgBRfWO1MMXqiYUKNUoC6s2GXGaIymzAdBgNVHQ4EFgQUiGGM
# oSo3ZIEoYKGbMdCM/SwCzk8wDgYDVR0PAQH/BAQDAgbAMAwGA1UdEwEB/wQCMAAw
# FgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwSgYDVR0gBEMwQTA1BgwrBgEEAbIxAQIB
# AwgwJTAjBggrBgEFBQcCARYXaHR0cHM6Ly9zZWN0aWdvLmNvbS9DUFMwCAYGZ4EM
# AQQCMEoGA1UdHwRDMEEwP6A9oDuGOWh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2Vj
# dGlnb1B1YmxpY1RpbWVTdGFtcGluZ0NBUjM2LmNybDB6BggrBgEFBQcBAQRuMGww
# RQYIKwYBBQUHMAKGOWh0dHA6Ly9jcnQuc2VjdGlnby5jb20vU2VjdGlnb1B1Ymxp
# Y1RpbWVTdGFtcGluZ0NBUjM2LmNydDAjBggrBgEFBQcwAYYXaHR0cDovL29jc3Au
# c2VjdGlnby5jb20wDQYJKoZIhvcNAQEMBQADggGBAAKBPqSGclEh+WWpLj1SiuHl
# m8xLE0SThI2yLuq+75s11y6SceBchpnKpxWaGtXc8dya1Aq3RuW//y3wMThsvT4f
# Sba2AoSWlR67rA4fTYGMIhgzocsids0ct/pHaocLVJSwnTYxY2pE0hPoZAvRebct
# bsTqENmZHyOVjOFlwN2R3DRweFeNs4uyZN5LRJ5EnVYlcTOq3bl1tI5poru9WaQR
# WQ4eynXp7Pj0Fz4DKr86HYECRJMWiDjeV0QqAcQMFsIjJtrYTw7mU81qf4FBc4u4
# swphLeKRNyn9DDrd3HIMJ+CpdhSHEGleeZ5I79YDg3B3A/fmVY2GaMik1Vm+FajE
# Mv4/EN2mmHf4zkOuhYZNzVm4NrWJeY4UAriLBOeVYODdA1GxFr1ycbcUEGlUecc4
# RCPgYySs4d00NNuicR4a9n7idJlevAJbha/arIYMEuUqTeRRbWkhJwMKmb9yEvpp
# RudKyu1t6l21sIuIZqcpVH8oLWCxHS0LpDRF9Y4jijCCBhQwggP8oAMCAQICEHoj
# rtpTaZYPkcg+XPTH4z8wDQYJKoZIhvcNAQEMBQAwVzELMAkGA1UEBhMCR0IxGDAW
# BgNVBAoTD1NlY3RpZ28gTGltaXRlZDEuMCwGA1UEAxMlU2VjdGlnbyBQdWJsaWMg
# VGltZSBTdGFtcGluZyBSb290IFI0NjAeFw0yMTAzMjIwMDAwMDBaFw0zNjAzMjEy
# MzU5NTlaMFUxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQx
# LDAqBgNVBAMTI1NlY3RpZ28gUHVibGljIFRpbWUgU3RhbXBpbmcgQ0EgUjM2MIIB
# ojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAzZjYQ0GrboIr7PYzfiY05ImM
# 0+8iEoBUPu8mr4wOgYPjoiIz5vzf7d5wu8GFK1JWN5hciN9rdqOhbdxLcSVwnOTJ
# mUGfAMQm4eXOls3iQwfapEFWuOsYmBKXPNSpwZAFoLGl5y1EaGGc5LByM8wjcbSF
# 52/Z42YaJRsPXY545E3QAPN2mxDh0OLozhiGgYT1xtjXVfEzYBVmfQaI5QL35cTT
# AjsJAp85R+KAsOfuL9Z7LFnjdcuPkZWjssMETFIueH69rxbFOUD64G+rUo7xFIdR
# AuDNvWBsv0iGDPGaR2nZlY24tz5fISYk1sPY4gir99aXAGnoo0vX3Okew4MsiyBn
# 5ZnUDMKzUcQrpVavGacrIkmDYu/bcOUR1mVBIZ0X7P4bKf38JF7Mp7tY3LFF/h7h
# vBS2tgTYXlD7TnIMPrxyXCfB5yQq3FFoXRXM3/DvqQ4shoVWF/mwwz9xoRku05ip
# hp22fTfjKRIVpm4gFT24JKspEpM8mFa9eTgKWWCvAgMBAAGjggFcMIIBWDAfBgNV
# HSMEGDAWgBT2d2rdP/0BE/8WoWyCAi/QCj0UJTAdBgNVHQ4EFgQUX1jtTDF6omFC
# jVKAurNhlxmiMpswDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw
# EwYDVR0lBAwwCgYIKwYBBQUHAwgwEQYDVR0gBAowCDAGBgRVHSAAMEwGA1UdHwRF
# MEMwQaA/oD2GO2h0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2VjdGlnb1B1YmxpY1Rp
# bWVTdGFtcGluZ1Jvb3RSNDYuY3JsMHwGCCsGAQUFBwEBBHAwbjBHBggrBgEFBQcw
# AoY7aHR0cDovL2NydC5zZWN0aWdvLmNvbS9TZWN0aWdvUHVibGljVGltZVN0YW1w
# aW5nUm9vdFI0Ni5wN2MwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28u
# Y29tMA0GCSqGSIb3DQEBDAUAA4ICAQAS13sgrQ41WAyegR0lWP1MLWd0r8diJiH2
# VVRpxqFGhnZbaF+IQ7JATGceTWOS+kgnMAzGYRzpm8jIcjlSQ8JtcqymKhgx1s6c
# FZBSfvfeoyigF8iCGlH+SVSo3HHr98NepjSFJTU5KSRKK+3nVSWYkSVQgJlgGh3M
# Pcz9IWN4I/n1qfDGzqHCPWZ+/Mb5vVyhgaeqxLPbBIqv6cM74Nvyo1xNsllECJJr
# OvsrJQkajVz4xJwZ8blAdX5umzwFfk7K/0K3fpjgiXpqNOpXaJ+KSRW0HdE0FSDC
# 7+ZKJJSJx78mn+rwEyT+A3z7Ss0gT5CpTrcmhUwIw9jbvnYuYRKxFVWjKklW3z83
# epDVzoWJttxFpujdrNmRwh1YZVIB2guAAjEQoF42H0BA7WBCueHVMDyV1e4nM9K4
# As7PVSNvQ8LI1WRaTuGSFUd9y8F8jw22BZC6mJoB40d7SlZIYfaildlgpgbgtu6S
# Dsek2L8qomG57Yp5qTqof0DwJ4Q4HsShvRl/59T4IJBovRwmqWafH0cIPEX7cEtt
# S5+tXrgRtMjjTOp6A9l0D6xcKZtxnLqiTH9KPCy6xZEi0UDcMTww5Fl4VvoGbMG2
# oonuX3f1tsoHLaO/Fwkj3xVr3lDkmeUqivebQTvGkx5hGuJaSVQ+x60xJ/Y29RBr
# 8Tm9XJ59AjCCBoIwggRqoAMCAQICEDbCsL18Gzrno7PdNsvJdWgwDQYJKoZIhvcN
# AQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5MRQwEgYD
# VQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3Jr
# MS4wLAYDVQQDEyVVU0VSVHJ1c3QgUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
# MB4XDTIxMDMyMjAwMDAwMFoXDTM4MDExODIzNTk1OVowVzELMAkGA1UEBhMCR0Ix
# GDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEuMCwGA1UEAxMlU2VjdGlnbyBQdWJs
# aWMgVGltZSBTdGFtcGluZyBSb290IFI0NjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
# ADCCAgoCggIBAIid2LlFZ50d3ei5JoGaVFTAfEkFm8xaFQ/ZlBBEtEFAgXcUmanU
# 5HYsyAhTXiDQkiUvpVdYqZ1uYoZEMgtHES1l1Cc6HaqZzEbOOp6YiTx63ywTon43
# 4aXVydmhx7Dx4IBrAou7hNGsKioIBPy5GMN7KmgYmuu4f92sKKjbxqohUSfjk1mJ
# lAjthgF7Hjx4vvyVDQGsd5KarLW5d73E3ThobSkob2SL48LpUR/O627pDchxll+b
# TSv1gASn/hp6IuHJorEu6EopoB1CNFp/+HpTXeNARXUmdRMKbnXWflq+/g36NJXB
# 35ZvxQw6zid61qmrlD/IbKJA6COw/8lFSPQwBP1ityZdwuCysCKZ9ZjczMqbUcLF
# yq6KdOpuzVDR3ZUwxDKL1wCAxgL2Mpz7eZbrb/JWXiOcNzDpQsmwGQ6Stw8tTCqP
# umhLRPb7YkzM8/6NnWH3T9ClmcGSF22LEyJYNWCHrQqYubNeKolzqUbCqhSqmr/U
# dUeb49zYHr7ALL8bAJyPDmubNqMtuaobKASBqP84uhqcRY/pjnYd+V5/dcu9ieER
# jiRKKsxCG1t6tG9oj7liwPddXEcYGOUiWLm742st50jGwTzxbMpepmOP1mLnJskv
# ZaN5e45NuzAHteORlsSuDt5t4BBRCJL+5EZnnw0ezntk9R8QJyAkL6/bAgMBAAGj
# ggEWMIIBEjAfBgNVHSMEGDAWgBRTeb9aqitKz1SA4dibwJ3ysgNmyzAdBgNVHQ4E
# FgQU9ndq3T/9ARP/FqFsggIv0Ao9FCUwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB
# /wQFMAMBAf8wEwYDVR0lBAwwCgYIKwYBBQUHAwgwEQYDVR0gBAowCDAGBgRVHSAA
# MFAGA1UdHwRJMEcwRaBDoEGGP2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VU0VS
# VHJ1c3RSU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDA1BggrBgEFBQcBAQQp
# MCcwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZI
# hvcNAQEMBQADggIBAA6+ZUHtaES45aHF1BGH5Lc7JYzrftrIF5Ht2PFDxKKFOct/
# awAEWgHQMVHol9ZLSyd/pYMbaC0IZ+XBW9xhdkkmUV/KbUOiL7g98M/yzRyqUOZ1
# /IY7Ay0YbMniIibJrPcgFp73WDnRDKtVutShPSZQZAdtFwXnuiWl8eFARK3PmLqE
# m9UsVX+55DbVIz33Mbhba0HUTEYv3yJ1fwKGxPBsP/MgTECimh7eXomvMm0/GPxX
# 2uhwCcs/YLxDnBdVVlxvDjHjO1cuwbOpkiJGHmLXXVNbsdXUC2xBrq9fLrfe8IBs
# A4hopwsCj8hTuwKXJlSTrZcPRVSccP5i9U28gZ7OMzoJGlxZ5384OKm0r568Mo9T
# YrqzKeKZgFo0fj2/0iHbj55hc20jfxvK3mQi+H7xpbzxZOFGm/yVQkpo+ffv5gdh
# p+hv1GDsvJOtJinJmgGbBFZIThbqI+MHvAmMmkfb3fTxmSkop2mSJL1Y2x/955S2
# 9Gu0gSJIkc3z30vU/iXrMpWx2tS7UVfVP+5tKuzGtgkP7d/doqDrLF1u6Ci3TpjA
# ZdeLLlRQZm867eVeXED58LXd1Dk6UvaAhvmWYXoiLz4JA5gPBcz7J311uahxCweN
# xE+xxxR3kT0WKzASo5G/PyDez6NHdIUKBeE3jDPs2ACc6CkJ1Sji4PKWVT0/MYIE
# kjCCBI4CAQEwajBVMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1p
# dGVkMSwwKgYDVQQDEyNTZWN0aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5nIENBIFIz
# NgIRAKQpO24e3denNAiHrXpOtyQwDQYJYIZIAWUDBAICBQCgggH5MBoGCSqGSIb3
# DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjUwNTE2MTYzOTUz
# WjA/BgkqhkiG9w0BCQQxMgQwkYn3jVUbpvYMfIjfzR/Lu/577mbixB/hInefALE4
# 13gCzB0Ds/1eZoqLqxYq6Ig9MIIBegYLKoZIhvcNAQkQAgwxggFpMIIBZTCCAWEw
# FgQUOMkUgRBEtNxmPpPUdEuBQYaptbEwgYcEFMauVOR4hvF8PVUSSIxpw0p6+cLd
# MG8wW6RZMFcxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQx
# LjAsBgNVBAMTJVNlY3RpZ28gUHVibGljIFRpbWUgU3RhbXBpbmcgUm9vdCBSNDYC
# EHojrtpTaZYPkcg+XPTH4z8wgbwEFIU9Yy2TgoJhfNCQNcSR3pLBQtrHMIGjMIGO
# pIGLMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UE
# BxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEu
# MCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQIQ
# NsKwvXwbOuejs902y8l1aDANBgkqhkiG9w0BAQEFAASCAgCdxXm5rxNUtO8+rjCY
# X1qTQRPI0GQmQrGJTh6qUSD2OJJ5RyBSc0FngvvCq2dMKYrwTbyhd6BWTBe7t0JJ
# xTPN1ioe0VcsgbkMIFwcuwZuvATDHOHdDOPfJlPnRSJvALyUPRfuCwAvAZbBZGV1
# HHB3yemtE8GF1P8EE1MSkLH3uFvabL5rJx6Qr2ec97oqv/nbK7B5met6M+4yQE4G
# VdV8qRYHsXz612+rJTf8y3pw6FDV48v8SpQ5Z080bHVB9Rcwt+L6Wxqw1iPIg5PS
# ApKfdgYngXWiBt+SYcc+Fx6KcXVbnyNX/yRNs9NOZzLE6wjZLrCcpnaJ33YDk0Xf
# TQ9ZtjLzC9EDhIt6hV/OZiyaXrye9gxgOjoKU39PQ//sjRg31ytf6cRr1ctkKyfg
# QrqQ46yKLr7Fm7Ug2WPrNaluWap0ralUI4GSdKHzYp9+/JFJ4vGB2UtDeX6GQQK+
# b1k2Q4dUtEUF10KhLx617vg3qWcrnqxbOKF3Tr//gw7yyLuXIGkFBFForpYfagUA
# o7XfcsmMotPHTn8pC6EgUuHs/tjJkMDNHzot/9qGj5IF46aHEJTTzteeTszEvqju
# ueppPyzqnDaqgUO1YU7Xrd2AJ7LfJnJ9rnQqgAGX8aOzVSBBuXxVr42w/VfyCd/Q
# xCqS5Q1w+SZWPJEcQhCaNxT+rw==
# SIG # End signature block
