dev.core.tds.psm1

Import-Module "$PSScriptRoot\dev.core.utils.psm1" -DisableNameChecking;
Import-ModuleIfNotExist ADAL.PS -Global;
Import-ModuleIfNotExist CredentialManager -Global;

$AdalToken = $null;
$TdsAppFolder = "$Home\AppData\Local\DevPowerShell\dev.core.tds";
$TdsTempFolder = "$env:TEMP\DevPowerShell\dev.core.tds"

$TdsInstance = [PSCustomObject]@{
    Environment = "Classic";
    Stack = "B";
    BaseUrl = "https://tdswebuistackb.azurewebsites.net";
}

# The RDP file template.
$RdpTemplate = "screen mode id:i:2
use multimon:i:0
desktopwidth:i:1440
desktopheight:i:900
session bpp:i:32
winposstr:s:0,1,451,102,1897,885
compression:i:1
keyboardhook:i:2
audiocapturemode:i:0
videoplaybackmode:i:1
connection type:i:2
displayconnectionbar:i:1
disable wallpaper:i:1
allow font smoothing:i:0
allow desktop composition:i:0
disable full window drag:i:1
disable menu anims:i:1
disable themes:i:0
disable cursor setting:i:0
bitmapcachepersistenable:i:1
audiomode:i:0
redirectprinters:i:1
redirectcomports:i:0
redirectsmartcards:i:1
redirectclipboard:i:1
redirectposdevices:i:0
redirectdirectx:i:1
autoreconnection enabled:i:1
authentication level:i:2
prompt for credentials:i:0
negotiate security layer:i:1
remoteapplicationmode:i:0
alternate shell:s:
shell working directory:s:
gatewayusagemethod:i:
gatewaycredentialssource:i:
gatewayprofileusagemethod:i:
promptcredentialonce:i:0
use redirection server name:i:0
administrative session:i:1
drivestoredirect:s:*
full address:s:__ADDRESS__
username:s:.\administrator
gatewayhostname:s:"
;


#
# Private Functions
#

function Logon-Tds
{
    if ($script:AdalToken -eq $null -or $script:AdalToken.ExpiresOn.DateTime -le (get-date).AddMinutes(-15))
    {
        Write-Verbose "Requesting authentication token";

        # Get oauth token for TDS API
        $authority = "https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47";
        $audience = "b51e4b8d-44f8-4beb-b426-d57ed395cb57";
        $clientId=  "2c883697-cfd4-46a5-9962-cdc74ba55125";
        $redirectUri = "urn:ietf:wg:oauth:2.0:oob";

        $script:AdalToken = Get-AdalToken -Resource $audience -ClientId $clientId -RedirectUri $redirectUri -Authority $authority -PromptBehavior Auto;
        
        Write-Verbose "Authentication token is acquired successfully.";    
    }
}

function Get-TdsUser
{
    Logon-Tds;

    return $script:AdalToken.UserInfo.DisplayableId;
}

function Invoke-TdsMethod
{
    [CmdletBinding()]
    param(
        [string]$PathAndQuery, 
        [Microsoft.PowerShell.Commands.WebRequestMethod]$Method = [Microsoft.PowerShell.Commands.WebRequestMethod]::Get,
        [object]$Body = $null);

    Logon-Tds;
    
    $uri = "$(${script:TdsInstance}.BaseUrl)/$PathAndQuery";
    $headers = @{ "Authorization" = "Bearer $($AdalToken.AccessToken)" };
    
    if ($Body)
    {
        Write-Verbose "body: $Body";
        Invoke-RestMethod -Method $Method -Uri $uri -Headers $headers -Body $Body -ContentType "application/json";
    }
    else
    {
        Invoke-RestMethod -Method $Method -Uri $uri -Headers $headers;
    }
}

function Get-ToplogyTypePayload
{
    param([PSCustomObject]$TopologyType);

    $environment = [PSCustomObject]@{
        "name" = "stack$($Script:TdsInstance.Stack.ToLower())";
        "display" = "CORP";
        "topoName" = $Script:TdsInstance.Environment;
        "clientId" = "b51e4b8d-44f8-4beb-b426-d57ed395cb57";
        "baseUrl" = $Script:TdsInstance.BaseUrl;
        "toolTip" = "";
    };

    $topologyTypeCopy = $TopologyType | ConvertTo-Json | ConvertFrom-Json;
    $topologyTypeCopy | Add-Member -MemberType NoteProperty -Name "environment" -Value $environment | Out-Null;

    return $topologyTypeCopy;
}

function Connect-TdsMachineInternal
{
    # param([string]$Name, [string]$IPAddress, [string]$Port, [string]$Password);
    [CmdletBinding()]
    param([PSTypeName("TDS.Machine")]$Machine);

    $Address = $Machine.IPAddress;
    $Port = $Machine.RdpPort;
    $Name = $Machine.Name;
    $Credential = $Machine.Credential;

    if ($Port -and $Port -ne "UNKNOWN")
    {
        $Address = "${Address}:${Port}";
    }

    $RdpContent = $script:RdpTemplate.Replace("__ADDRESS__", $Address);

    $RdpFilePath = "${TdsTempFolder}\${Name}.rdp";

    Write-Verbose "Writing temporary RDP file ${RdpFilePath}";
    Set-Content -Path $RdpFilePath -Value $RdpContent;

    # Store the credential in local machine credential manager
    New-StoredCredential -Target $Machine.IPAddress -UserName $Credential.UserName -SecurePassword $Credential.Password -Type Generic -Persist LocalMachine | Out-Null;

    Invoke-Item -Path $RdpFilePath;
}

function Clean-TdsMachine
{
    [CmdletBinding()]
    param([PSTypeName("TDS.Machine")]$Machine);

    # Remove TDS rdp file.
    $Name = $Machine.Name;
    $RdpFilePath = "${TdsTempFolder}\${Name}.rdp";
    if (Test-Path $RdpFilePath)
    {
        Remove-Item -Path $RdpFilePath;
    }

    # Clear stored credential.
    Remove-StoredCredential -Target $Machine.IPAddres -ErrorAction SilentlyContinue;
}

function Update-TdsMachineDnsEntryInternal
{
    param([string]$Name, [string]$IPAddress);
    
    $hostsFile = "${env:SystemRoot}\system32\drivers\etc\hosts";
    $lines = Get-Content -Path $hostsFile;

    $machineDnsLine = "${IPAddress}`t${Name}";
    $done = $false;
    for ($i = 0; $i -lt $lines.Length; $i++)
    {
        $line = $lines[$i];

        if ($line.Trim().StartsWith("#"))
        {
            continue;
        }

        if ($line.Contains($Name))
        {
            $lines[$i] = $machineDnsLine;
            $done = $true;
        }
    }

    if (!$done)
    {
        $lines += $machineDnsLine;
    }

    Set-Content -Path $hostsFile -Value $lines;
}

function ConvertTo-TdsMachine
{
    param([Parameter(ValueFromPipeline=$true)][PSCustomObject]$Machine);

    $DomainName = "$($Machine.Name)DOM.EXTEST.MICROSOFT.COM";
    $UserName = "$DomainName\Administrator";
    $SecurePassword = ConvertTo-SecureString -String $Machine.VmAdmin -AsPlainText -Force;
    $Credential = New-Object System.Management.Automation.PSCredential($UserName, $SecurePassword);

    return [PSCustomObject]@{
        PSTypeName = "TDS.Machine";
        Name = $Machine.Name;
        BuildNumber = $Machine.BuildNumber;
        Fqdn = $Machine.Fqdn;
        IPAddress = $Machine.IPAddress;
        OperatingSystem = $Machine.OperatingSystem;
        RdpPort = $Machine.RdpPort;
        WrmPort = $Machine.WrmPort;
        Credential = $Credential;
    };
}

function ConvertTo-TdsTopology
{
    param([Parameter(ValueFromPipeline=$true)][PSCustomObject]$Topology);

   return [PSCustomObject]@{
        PSTypeName = "TDS.Topology";
        TopologyId = $Topology.TopologyId;
        WorkgroupId = $Topology.WorkgroupId;
        Name = $Topology.Name;
        Owner = $Topology.Owner;
        TopologyState = $Topology.State;
        TopologyType = $Topology.TopologyType;
        Machines = ($Topology.MachinesExt | ConvertTo-TdsMachine);
        CreatedTime = [datetime]($Topology.CreatedTime);
        RecoverTime = [datetime]($Topology.RecoverTime);
        WarningTime = [datetime]($Topology.WarningTime);
    };
}

$TdsMachinePair = $null;
function New-TdsMachinePairInternal
{
    param([PSCustomObject]$Machine);
    
    $dnsEntries = @(
        (New-LocalDnsEntry -HostName $Machine.Name -IPAddress $Machine.IPAddress)
    );

    $dnsEntries | Set-LocalDnsEntry;

    Clear-DnsClientCache;

    $session = $Machine | New-TdsMachinePsSession;

    Invoke-Command -Session $session -ScriptBlock { Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010 }

    # TODO: Download the sti certificate from TDS machine and install to LOCAL_MACHINE\MY
    
    $script:TdsMachinePair = [PSCustomObject]@{
        PSTypeName = "TDS.MachinePair";
        Machine = $Machine;
        Session = $session;
        Data = @{};
        PairedTime = (Get-Date);
    };

    return $script:TdsMachinePair;
}

function Ensure-TdsMachinePair
{
    if (!$script:TdsMachinePair)
    {
        throw "TDS machine is not paired";
    }
}

function Invoke-PairedCommand
{
    param(
        [scriptblock]$ScriptBlock,
        [psobject]$InputObject,
        [object[]]$ArgumentList);
    Ensure-TdsMachinePair;

    Invoke-Command -Session $script:TdsMachinePair.Session -ScriptBlock $ScriptBlock -InputObject $InputObject -ArgumentList $ArgumentList;
}

#
# Public Functions
#

function Set-TdsInstance
{
    [CmdletBinding()]
    param(
        [ValidateSet("Classic", "Cloud")][string]$Environment = "Classic",
        [ValidateSet("A", "B")][string]$Stack = "B"
    );

    $s = $Stack.ToLower();
    $BaseUrl = switch ($Environment)
    {
        "Classic" { "https://tdswebuistack${s}.azurewebsites.net" }
        "Cloud" { "https://uw2tdsprdapi-for-stack${s}.azurewebsites.net" }
    }
    
    $script:TdsInstance = [PSCustomObject]@{
        Environment = $Environment;
        Stack = $Stack;
        BaseUrl = $BaseUrl;
    }
}

function Get-TdsInstance
{
    [CmdletBinding()]
    param();

    return $script:TdsInstance;
}

function Ping-Tds
{
    Invoke-TdsMethod -PathAndQuery "api/servicestatus";
}

function Get-TdsTopologyType
{
    [CmdletBinding(DefaultParameterSetName="GetAll")]
    param(
        [Parameter(ParameterSetName="ByName")][string]$Name, 
        [Parameter(ParameterSetName="ById")][int]$Id,
        [switch]$Refresh);

    # Try to load topology types from local cache file first.
    $TopologyTypesJsonFilePath = "${script:TdsAppFolder}\topologytypes.json";
    $CacheFileAvailable = Test-Path $TopologyTypesJsonFilePath;
    if ($CacheFileAvailable)
    {
        # The topology types local cache file expires in 24 hours.
        $FileItem = Get-Item -Path $TopologyTypesJsonFilePath;
        if ($FileItem.LastWriteTime.AddDays(1) -lt (Get-Date))
        {
            Write-Verbose "Typology topology types local cache file ${TopologyTypesJsonFilePath} has expired.";
            $CacheFileAvailable = $false;
        }
    }
    else
    {
        Write-Verbose "Typology topology types local cache file ${TopologyTypesJsonFilePath} not found.";
    }

    if ($Refresh -or !$CacheFileAvailable)
    {
        $topologyTypes = Invoke-TdsMethod -PathAndQuery "api/topologytype/enumerate";

        Write-Verbose "Updating topology types local cache file ${TopologyTypesJsonFilePath}...";
        $topologyTypes | ConvertTo-Json | Out-File -FilePath $TopologyTypesJsonFilePath;
    }

    if (!$topologyTypes)
    {
        $topologyTypes = Get-Content -Path $TopologyTypesJsonFilePath -Raw | ConvertFrom-Json; 
    }

    if ($PSCmdlet.ParameterSetName -eq "ByName")
    {
        $topologyTypes = $topologyTypes | ? { $_.DisplayName -like $Name };
    }
    elseif ($PSCmdlet.ParameterSetName -eq "ById")
    {
        $topologyTypes = $topologyTypes | ? { $_.Id -eq $Id };
    }

    foreach ($topologyType in $topologyTypes)
    {
        Write-Output $topologyType;
    }
}

function Get-TdsTopologyTypeBuild
{
    [CmdletBinding()] 
    param(
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="ByTopologyType")][PSCustomObject]$TopologyType,
        [Parameter(Mandatory=$true, ParameterSetName="ByTopologyTypeId")][int]$TopologyTypeId,
        [Parameter(Mandatory=$true, ParameterSetName="ByTopologyTypeName")][string]$TopologyTypeName
    );

    if ($PSCmdlet.ParameterSetName -eq "ByTopologyTypeId")
    {
        $TopologyType = Get-TdsTopologyType | ? { $_.Id -eq $TopologyTypeId };
        if (!$TopologyType)
        {
            throw "Topology type with id ${TopologyTypeId} not found";
        }
    }
    elseif ($PSCmdlet.ParameterSetName -eq "ByTopologyTypeName")
    {
        $TopologyType = Get-TdsTopologyType | ? { $_.DisplayName -eq $TopologyTypeName };
        if (!$TopologyType)
        {
            throw "Topology type with name ${TopologyTypeName} not found";
        }
    }

    $payload = Get-ToplogyTypePayload -TopologyType $TopologyType;

    $builds = Invoke-TdsMethod -Method Post -PathAndQuery "api/buildnumber/bytopologytype" -Body (ConvertTo-Json $payload);
    $builds | % { Write-Output $_ };
}

function New-TdsTopology
{
    [CmdletBinding(DefaultParameterSetName="ByTopologyTypeId")] 
    param(
        [Parameter(Position=0, Mandatory=$true, ParameterSetName="ByTopologyTypeId")][int]$TopologyTypeId,
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="ByTopologyType")][PSCustomObject]$TopologyType,
        [Parameter(Mandatory=$true, ParameterSetName="ByTopologyTypeName")][string]$TopologyTypeName,
        [string]$Build
    );

    if ($PSCmdlet.ParameterSetName -eq "ByTopologyTypeId")
    {
        $TopologyType = Get-TdsTopologyType | ? { $_.Id -eq $TopologyTypeId };
        if (!$TopologyType)
        {
            throw "Topology type with id ${TopologyTypeId} not found";
        }
    }
    elseif ($PSCmdlet.ParameterSetName -eq "ByTopologyTypeName")
    {
        $TopologyType = Get-TdsTopologyType | ? { $_.DisplayName -eq $TopologyTypeName };
        if (!$TopologyType)
        {
            throw "Topology type with name ${TopologyTypeName} not found";
        }
    }

    if (!$Build)
    {
        $builds = Get-TdsTopologyTypeBuild -TopologyType $TopologyType;
        if (!$builds)
        {
            throw "No build available for topology type $($TopologyType.DisplayName).";
        }

        $Build = $builds[0]; # The first build is the latest build.
        Write-Verbose "Using latest build ${Build} for topology type $($TopologyType.DisplayName).";
    }

    $payload = [PSCustomObject]@{
        "TopologyType" = (Get-ToplogyTypePayload -TopologyType $TopologyType);
        "UserData" = $null;
    };

    $topologyOwner = Get-TdsUser;

    $path = "api/topology/checkoutWithUserData";
    $query = "topologyName=$($TopologyType.DisplayName) ${Build}&topologyOwner=${topologyOwner}&buildNumber=${Build}&source=v2website";

    Invoke-TdsMethod -Method Post -PathAndQuery "${Path}?${Query}" -Body (ConvertTo-Json $payload) | Out-Null;

    # The REST API doesn't return response, so get it back by finding the topology with biggest id.
    Get-TdsTopology | Sort-Object -Property TopologyId -Descending | Select-Object -First 1;
}

function Get-TdsTopology
{
    [CmdletBinding()]
    param([string]$TopologyId);

    $topologies = Invoke-TdsMethod -PathAndQuery "api/jobs/byownerwithpasswords?owner=$(Get-TdsUser)";
    foreach ($topology in $topologies)
    {
        if (!$PSBoundParameters.ContainsKey("TopologyId") -or $TopologyId -eq $topology.TopologyId)
        {
            Write-Output (ConvertTo-TdsTopology -Topology $topology);
        }
    }
}

function Remove-TdsTopology
{
    [CmdletBinding()]
    param(
        [Parameter(Position=0, Mandatory=$true, ValueFromPipeline)][PSTypeName('TDS.Topology')]$Topology,
        [bool]$Confirm = $true
    );

    begin
    {
        $removeAll = !$Confirm;
    }

    process
    {
        $WorkgroupId = $Topology.WorkgroupId;
        $shouldRemove = $removeAll;
        if (!$shouldRemove)
        {
            # Display confirmation prompt
            $caption = "Remove Topology";
            $message = "Are you sure to remove TDS topology ${WorkgroupId}:"
            [int]$defaultChoice = 1; # No
            $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes";
            $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No";
            $all = New-Object System.Management.Automation.Host.ChoiceDescription "&All";
            $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no, $all);
            $choice = $host.UI.PromptForChoice($caption, $message, $options, $defaultChoice);

            if ($choice -eq 0) # Yes
            {
                $shouldRemove = $true;
            }
            elseif ($choice -eq 2) # All
            {
                $removeAll = $true;
                $shouldRemove = $true;
            }
        }

        if ($shouldRemove)
        {
            Write-Verbose "Deleting topology with workgroup id ${WorkgroupId}";
            Invoke-TdsMethod -PathAndQuery "api/topology/checkin?topologyId=${WorkgroupId}";
        }     
    }
}

function Get-TdsMachine
{
    [CmdletBinding()]
    param([string]$Name);

    $topologies = Get-TdsTopology;
    foreach ($topology in $topologies)
    {
        foreach ($machine in $topology.Machines)
        {
            if (!$Name -or $Name -eq $machine.Name)
            {
                Write-Verbose "Found TDS machine $($machine.Name)";
                Write-Output $machine;
            }
        }
    }
}

function Resolve-TdsMachine
{
    [CmdletBinding()]
    param(
        [PSCustomObject]$TopologyOrMachine,
        [string]$Name,
        [Hashtable]$State
    );
    
    if ($TopologyOrMachine)
    {
        # The input object could be a TDS topology or a TDS machine
        if ($TopologyOrMachine.PSTypeNames -contains "TDS.Topology")
        {
            foreach ($machine in $TopologyOrMachine.Machines)
            {
                Write-Output $machine;
            }
        }
        elseif ($TopologyOrMachine.PSTypeNames -contains "TDS.Machine")
        {
            Write-Output $TopologyOrMachine;
        }
        elseif ($TopologyOrMachine.PSTypeNames -contains "TDS.MachinePair")
        {
            Write-Output $TopologyOrMachine.Machine;
        }
    }
    else
    {
        $machines = $null;

        # Try to get machine list from external state first.
        if ($State)
        {
            if (!$State.Machines)
            {
                $State.Machines = Get-TdsMachine;
            }

            $machines = $State.Machines;
        }

        if (!$machines)
        {
            $machines = Get-TdsMachine;
        }

        $machine = $machines | ? { $_.Name -eq $Name };
        if (!$machine)
        {
            throw "TDS machine ${Name} not found.";
        }

        Write-Output $machine;
    }
}

function Connect-TdsMachine
{
    [CmdletBinding(DefaultParameterSetName="ByName")]
    param(
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="ByTopologyOrMachine")][PSCustomObject]$TopologyOrMachine,
        [Parameter(Position=0, Mandatory=$true, ParameterSetName="ByName")][string]$Name
    );

    begin
    {
        $State = @{};
    }

    process
    {
        $machines = Resolve-TdsMachine -TopologyOrMachine $TopologyOrMachine -Name $Name -State $State;
        foreach ($machine in $machines)
        {
            Write-Verbose "Connecting to machine $($machine.Name)";
            Connect-TdsMachineInternal -Machine $machine;
        }
    }
}

function New-TdsMachinePsSession
{
    [CmdletBinding(DefaultParameterSetName="ByName")]
    param(
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="ByTopologyOrMachine")][PSCustomObject]$TopologyOrMachine,
        [Parameter(Position=0, Mandatory=$true, ParameterSetName="ByName")][string]$Name
    );
    
    begin
    {
        $State = @{};
    }

    process
    {
        $machines = Resolve-TdsMachine -TopologyOrMachine $TopologyOrMachine -Name $Name -State $State;
        foreach ($machine in $machines)
        {            
            Write-Verbose "Creating PSSession to machine $($machine.Name)";
            New-PSSession -ComputerName $machine.Name -Credential $machine.Credential;
        }
    }
}

function Set-TdsMachineDnsEntry
{
    [CmdletBinding(DefaultParameterSetName="ByName")]
    param(
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="ByTopologyOrMachine")][PSCustomObject]$TopologyOrMachine,
        [Parameter(Position=0, Mandatory=$true, ParameterSetName="ByName")][string]$Name
    );

    begin
    {
        $State = @{};
    }

    process
    {
        $machines = Resolve-TdsMachine -TopologyOrMachine $TopologyOrMachine -Name $Name -State $State;
        foreach ($machine in $machines)
        {
            Write-Verbose "Updating $($machine.Name)=$($machine.IPAddress) DNS entry";
            Set-LocalDnsEntry -HostName $machine.Name -IPAddress $machine.IPAddress;
        }
    }
}


#
# Public Functions
#
function Test-TdsMachinePair
{    
    [CmdletBinding()]
    param();
    
    return $script:TdsMachinePair -ne $null;
}

function Get-TdsMachinePair
{    
    [CmdletBinding()]
    param();
    
    if ($script:TdsMachinePair)
    {
        return $script:TdsMachinePair;
    }
}

function Set-TdsMachinePair
{    
    [CmdletBinding(DefaultParameterSetName="ByName")]
    param(
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="ByTopologyOrMachine")][PSCustomObject]$TopologyOrMachine,
        [Parameter(Position=0, Mandatory=$true, ParameterSetName="ByName")][string]$Name
    );

    begin
    {
        $State = @{};
        $First = $true;

        Reset-TdsMachinePair;
    }

    process
    {
        $machines = Resolve-TdsMachine -TopologyOrMachine $TopologyOrMachine -Name $Name -State $State;
        foreach ($machine in $machines)
        {
            if (!$First)
            {
                Write-Warning "Skipping pairing with $($machine.Name), only one machine is allowed to pair.";
            }

            Write-Verbose "Connecting to machine $($machine.Name)";
            New-TdsMachinePairInternal -Machine $machine;

            $First = $false;
        }
    }
}

function Reset-TdsMachinePair
{
    [CmdletBinding()]
    param();

    if ($script:TdsMachinePair)
    {
        Write-Verbose "Removing paired PSSession...";
        Remove-PSSession -Session $script:TdsMachinePair.Session -ErrorAction SilentlyContinue;
    }

    $script:TdsMachinePair = $null;
}

# Test if the current machine is TDS machine
function Test-IsTdsMachine
{
    [CmdletBinding()]
    param();

    return $env:USERNAME -eq "Administrator" -and $env:USERDNSDOMAIN.EndsWith(".EXTEST.MICROSOFT.COM");
}

#
# Module Initialization
#

New-DirectoryIfNotExist -Path $TdsAppFolder | Out-Null;
New-DirectoryIfNotExist -Path $TdsTempFolder | Out-Null;

$OnRemoveScript = {
    Write-Warning "Removing TDS machine pair...";
    Reset-TdsMachinePair;
}

$ExecutionContext.SessionState.Module.OnRemove += $OnRemoveScript;


Export-ModuleMember -Function Get-TdsInstance;
Export-ModuleMember -Function Set-TdsInstance;
Export-ModuleMember -Function Ping-Tds;
Export-ModuleMember -Function Get-TdsTopologyType;
Export-ModuleMember -Function Get-TdsTopologyTypeBuild;
Export-ModuleMember -Function New-TdsTopology;
Export-ModuleMember -Function Get-TdsTopology;
Export-ModuleMember -Function Remove-TdsTopology;
Export-ModuleMember -Function Get-TdsMachine;
Export-ModuleMember -Function Connect-TdsMachine;
Export-ModuleMember -Function Resolve-TdsMachine;
Export-ModuleMember -Function New-TdsMachinePsSession;
Export-ModuleMember -Function Set-TdsMachineDnsEntry;
Export-ModuleMember -Function Get-TdsMachinePair;
Export-ModuleMember -Function Set-TdsMachinePair;
Export-ModuleMember -Function Reset-TdsMachinePair;
Export-ModuleMember -Function Test-TdsMachinePair;
Export-ModuleMember -Function Test-IsTdsMachine;