VMware.LifecycleMatrix.psm1

function _init {

    if ( $psversiontable.psedition -eq "Desktop" ) {
        # Add TLS1.2 support
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12, [Net.SecurityProtocolType]::Tls, [Net.SecurityProtocolType]::Ssl3

        ## Define class required for certificate validation override. Version dependant.
        ## For whatever reason, this does not work when contained within a function?
        $TrustAllCertsPolicy = @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
            }
        }
"@

    
        if ( -not ("TrustAllCertsPolicy" -as [type])) {
            Add-Type $TrustAllCertsPolicy
        }
    
        $script:originalCertPolicy = [System.Net.ServicePointManager]::CertificatePolicy
    }
}

Function Get-CurrentDate {
    $date = Get-Date
    $date.toString('MM/dd/yyyy')
}

function Add-UriQueryParam {
    param (
        [Parameter (Mandatory = $true)]
        [object]$QueryObject,
        [Parameter (Mandatory = $true)]
        [ValidateNotNullorEmpty()]
        [string[]]$QueryString
    )

    foreach ($queryToAppend in $QueryString) {
        if ( ($null -ne $QueryObject) -AND ($QueryObject.Length -gt 1) ) {
            if ($QueryObject.contains($queryToAppend)) {
                Write-Verbose "QueryObject already contains $queryToAppend. Not adding."
            }
            else {
                if ($QueryObject.StartsWith('?')) {
                    $QueryObject = $QueryObject.Substring(1) + "&" + $queryToAppend
                }
                else {
                    $QueryObject = $QueryObject + "&" + $queryToAppend
                }
            }
        }
        else {
            $QueryObject = $queryToAppend; 
        }
    }
    $QueryObject
}

Function Get-PLMToken {
    param (
        [Parameter (Mandatory = $True)]
        [ValidateNotNullorEmpty()]
        $Server,
        [Parameter (Mandatory = $True)]
        [ValidateNotNullorEmpty()]
        $Protocol,
        [Parameter (Mandatory = $True)]
        [ValidateNotNullorEmpty()]
        $ClientId,
        [Parameter (Mandatory = $True)]
        [ValidateNotNullorEmpty()]
        $ClientSecret,
        [Parameter (Mandatory = $True)]
        [ValidateNotNullorEmpty()]
        $GrantType
    )

    $headers = @{}
    $headers.Add("Content-Type", "application/json")

    $baseUri = New-Object System.UriBuilder("$($Protocol)://$($Server)")
    $baseUri.path = "/api/auth/v1/tokens"

    $body = @{
        "grant_type"    = $GrantType;
        "client_id"     = $ClientId;
        "client_secret" = $ClientSecret;
    }

    Write-Debug $($headers | ConvertTo-Json -Depth 100)
    Write-Debug $($body | ConvertTo-Json -Depth 100)

    Try {
        $response = Invoke-RestMethod -Method POST -Uri $baseUri.Uri -Headers $headers -Body $($body | ConvertTo-Json -Depth 100)
    }
    Catch {
        Write-Debug $_
        Throw "Unable to retrieve PLM Token."
    }

    Write-Debug $($response | ConvertTo-Json -Depth 100)
    $response
}

Function Get-DateFromString {
    param (
        [Parameter (Mandatory = $true)]
        [AllowEmptyString()]
        [string]$Date
    )
    if ($date) {
        [Datetime]::ParseExact($Date, 'yyyy-MM-dd', $null);
    }
}



function New-PLMProductItem {
    param (
        [Parameter (Mandatory = $true)]
        [ValidateNotNullorEmpty()]
        [object[]]$Item,
        [Parameter (Mandatory = $true)]
        [ValidateSet("Supported", "Unsupported")]
        [string]$Status
    )

    foreach ($x in $item) {
        $object = [PSCustomobject]@{
            "Name"     = $x.name;
            "Status"   = $Status;
            
            "GADate"   = Get-DateFromString -Date $x.ga_date;
            "EOSDate"  = Get-DateFromString -Date $x.end_support_date;
    
            "EOTGDate" = Get-DateFromString -Date $x.end_tech_guidance_date;
            "EOADate"  = Get-DateFromString -Date $x.end_availability_date;
    
            "Policy"   = $x.lifecycle_policy.abbreviation
            "Notes"    = $null
        }
        $footnoteArray = New-Object System.Collections.ArrayList
        if ($x.footnotes) {
            foreach ($footnote in $x.footnotes) {
                $footnoteArray.Add($footnote.description) | Out-Null
            }
            $object.Notes = $footnoteArray
        }
        $object
    }
}

Function Invoke-InterestingDateCheck {
    param (
        [Parameter (Mandatory = $true)]
        [ValidateNotNullorEmpty()]
        [object]$ProductItem,
        [Parameter (Mandatory = $False)]
        [ValidateNotNullorEmpty()]
        [datetime]$Date
    )

    $currentDate = Get-Date
    $DateFieldNames = $ProductItem | Get-Member -MemberType Properties -Name '*Date*'

    if ($Date.date -lt $currentDate.date) {
        foreach ($DateFieldName in $DateFieldNames.name) {
            if ($ProductItem.$DateFieldName) {
                if (Get-BetweenTwoDates -Date $ProductItem.$DateFieldName -Start $Date.date -End $currentDate.date) {
                    return $True
                }
            }

        }
    }
    elseif ($Date.date -gt $currentDate.date) {
        foreach ($DateFieldName in $DateFieldNames.name) {
            if ($ProductItem.$DateFieldName) {
                if (Get-BetweenTwoDates -Date $ProductItem.$DateFieldName -Start $currentDate.date -End $Date.date) {
                    return $True
                }
            }
        }
    }
    else {
        foreach ($DateFieldName in $DateFieldNames.name) {
            if ($ProductItem.$DateFieldName) {
                if ($(Get-Date $ProductItem.$DateFieldName).date -eq $(Get-Date).date) {
                    return $True
                }
            }
        }
    }
    return $False
}

Function Get-BetweenTwoDates {
    param (
        $Date,
        $Start,
        $End
    )

    if ( ($Date -ge $Start) -AND ($Date -le $End) ) {
        $True
    }
    else {
        $False
    }
}

Function Get-FilteredResults {
    param (
        [Parameter (Mandatory = $true)]
        [ValidateNotNullorEmpty()]
        [object[]]$results,
        [Parameter (Mandatory = $true)]
        [ValidateSet("Supported", "Unsupported")]
        [string[]]$Status,
        [Parameter (Mandatory = $False)]
        [ValidateNotNullorEmpty()]
        [string]$Name,
        [Parameter (Mandatory = $False)]
        [ValidateNotNullorEmpty()]
        [datetime]$Date
    )

    foreach ($statusItem in $status ) {
        if ($Name) {
            $item = $results."$statusItem" | Where-Object { $($_.name).trim() -like $Name.trim() }
            if ($item) {
                New-PLMProductItem -Item $item -Status $statusItem
            }
        }
        else {
            foreach ($item in $results."$statusItem") {
                New-PLMProductItem -Item $item -Status $statusItem 
            }
        }
    }
}

Function Get-InterestingResults {
    param (
        [Parameter (Mandatory = $True)]
        [ValidateNotNullorEmpty()]
        [object[]]$results,
        [Parameter (Mandatory = $True)]
        [ValidateNotNullorEmpty()]
        [datetime]$Date
    )

    foreach ($item in $results) {
        if (Invoke-InterestingDateCheck -ProductItem $Item -Date $Date) {
            $Item
        }
    }
}

################################################################################
# Functions to export
################################################################################

Function Connect-PLMServer {
    <#
    .SYNOPSIS
    Creates a connection to the VMware Product Lifecycle Matrix server.

    .DESCRIPTION
    Creates a connection to the VMware Product Lifecycle Matrix server. Once a
    connection has been established via this cmdlet, the details are saved in a
    connection object called $DefaultPLMConnection with a scope of Global.
    
    .PARAMETER Quiet
    When specified, the resulting connection object is not displayed. This is
    helpful when being used in a script.
    
    .EXAMPLE
    Connect-PLMServer

    Authenticate to the PLM Authentication server and retrieve the access token
    
    .EXAMPLE
    Connect-PLMServer -Quiet

    Authenticate to the PLM Authentication server and retrieve the access token,
    but do not display the resulting connection object.

    .NOTES
    Author(s): Dale Coghlan
    Twitter: @DaleCoghlan
    Github: dcoghlan

    Although this cmdlet doesn't require any parameters to be set today (as these
    are currently using default values), it has been written for if/when VMware
    decideds to allow customers to login to the server and get a curated list of
    products that the customer owns, rather than the customer having to find
    their own products themselves.

    .LINK
    https://github.com/dcoghlan/VMware.LifecycleMatrix
    #>


    [CmdLetBinding(DefaultParameterSetName = "Unofficial")]

    param (
        [Parameter (Mandatory = $False, ParameterSetName = "Unofficial")]
        [ValidateNotNullorEmpty()]
        [String]$Protocol = "https",
        [Parameter (Mandatory = $False, ParameterSetName = "Unofficial")]
        [ValidateNotNullorEmpty()]
        [String]$Server = "auth.esp.vmware.com",
        [Parameter (Mandatory = $False, ParameterSetName = "Unofficial")]
        [ValidateNotNullorEmpty()]
        [String]$ClientId = 'plm-prod-auth',
        [Parameter (Mandatory = $False, ParameterSetName = "Unofficial")]
        [ValidateNotNullorEmpty()]
        [String]$ClientSecret = '84e0f320-5c9d-4ced-a99b-3cc0a7ad64a9',
        [Parameter (Mandatory = $False, ParameterSetName = "Unofficial")]
        [ValidateNotNullorEmpty()]
        [String]$GrantType = "client_credentials",
        [Parameter (Mandatory = $False, ParameterSetName = "Unofficial")]
        [Switch]$Quiet
        
    )
    
    $TokenResponse = Get-PLMToken -ClientId $ClientId -ClientSecret $ClientSecret -GrantType $GrantType -Server $Server -Protocol $Protocol
    $token = $TokenResponse.access_token

    #Setup the connection object
    $connection = [pscustomObject] @{
        "AuthServer" = $Server;
        "PLMServer"  = "plm.esp.vmware.com";
        "ClientId"   = $ClientId;
        "PLMToken"   = $token;
        "Protocol"   = $Protocol;
        # "DebugLogging" = $DebugLogging
        # "DebugLogfile" = $DebugLogFile
    }

    #Set the default connection is required.
    Set-Variable -Name DefaultPLMConnection -Value $connection -Scope Global

    if (!$Quiet) {
        $connection
    }
}

Function Get-PLMProduct {
    <#
    .SYNOPSIS
    Retrieve the list of products available on the VMware Lifecycle Matrix website.
    
    .DESCRIPTION
    Retrieve the list of products available on the VMware Lifecycle Matrix website.
    
    .PARAMETER Supported
    When specified, the -Supported switch will return only products marked by
    the website as supported.
    
    .PARAMETER Unsupported
    When specified, the -Unsupported switch will return only products marked by
    the website as unsupported.
    
    .PARAMETER Name
    When specified, only the products matching the name will be returned. Can be
    used with the -Supported and -Unsupported switches to further reduce the
    scope of the search.
    
    .EXAMPLE
    Get-PLMProduct

    Retrieve all the products listed on the VMware Lifecycle Matix website.
    
    .EXAMPLE
    Get-PLMProduct | FT

    Retrieve all the products listed on the VMware Lifecycle Matix website and
    display them in a nicely formatted table.

    .EXAMPLE
    Get-PLMProduct -Supported

    Retrieve all the supported products

    .EXAMPLE
    Get-PLMProduct -Unsupported

    Retrieve all the unsupported products

    .EXAMPLE
    Get-PLMProduct -Name 'vSphere Replication 5.1'

    Retrieve details on the product 'vSphere Replication 5.1'

    .EXAMPLE
    Get-PLMProduct -Name '*replication*'

    Retrieve details on all products that have 'replication' in the name

    .EXAMPLE
    Get-PLMProduct -Name '*replication*' -Supported

    Retrieve details on all supported products that have 'replication' in the name

    .EXAMPLE
    Get-PLMProduct -Name 'nsx*'

    Retrieve details on all products where the name starts with 'nsx'

    .NOTES
    Author(s): Dale Coghlan
    Twitter: @DaleCoghlan
    Github: dcoghlan

    .LINK
    https://github.com/dcoghlan/VMware.LifecycleMatrix
    #>

    [CmdLetBinding(DefaultParameterSetName = "Default")]

    param (
        [Parameter (Mandatory = $False, ParameterSetName = "Default")]
        [Parameter (Mandatory = $False, ParameterSetName = "Name")]
        [ValidateNotNullorEmpty()]
        [switch]$Supported,
        [Parameter (Mandatory = $False, ParameterSetName = "Default")]
        [Parameter (Mandatory = $False, ParameterSetName = "Name")]
        [ValidateNotNullorEmpty()]
        [switch]$Unsupported,
        [Parameter (Mandatory = $True, ParameterSetName = "Name")]
        [ValidateNotNullorEmpty()]
        [string]$Name,
        [Parameter (Mandatory = $False)]
        [ValidateNotNullOrEmpty()]
        [PSCustomObject]$Connection = $DefaultPLMConnection

    )

    if (!$Connection) {
        Throw "Unable to connection to PLM Server. Please use Connect-PLMServer first."
    }
    # $headers = New-Object 'System.Collections.Generic.Dictionary[String,object]'
    $headers = @{}
    $headers.Add("Content-Type", "application/json")
    $headers.Add("x-auth-key", $DefaultPLMConnection.PLMToken)

    $baseUri = New-Object System.UriBuilder("$($DefaultPLMConnection.Protocol)://$($DefaultPLMConnection.PLMServer)")
    $baseUri.path = "/api/v1/release_stream/lifecycle_matrix"
    $baseUri.Query = Add-UriQueryParam -QueryObject $baseUri.Query -QueryString "to=$(Get-CurrentDate)"
    try {
        $response = Invoke-RestMethod -Uri $baseUri.Uri -Headers $headers
    }
    catch {
        throw "Unable to retieve Product Lifecycle Matrix data."
    }

    if ($response) {
        Write-Debug $($response | ConvertTo-Json -Depth 100)
        $splat = @{
            "results" = $response
        }
        if ( ($supported) -AND (!$Unsupported) ) {
            Write-Verbose "Get-PLMProduct(): Get filtered results for Supported products"
            $splat['Status'] = "Supported"
            if ($name) {
                Write-Verbose "Get-PLMProduct(): Adding name to filter the results"
                $splat['Name'] = $name
            }
            Get-FilteredResults @splat
        }
        elseif ( ($Unsupported) -AND (!$supported) ) {
            Write-Verbose "Get-PLMProduct(): Get filtered results for unupported products"
            $splat['Status'] = "Unsupported"
            if ($name) {
                Write-Verbose "Get-PLMProduct(): Adding name to filter the results"
                $splat['Name'] = $name
            }
            Get-FilteredResults @splat
        }
        else {
            Write-Verbose "Get-PLMProduct(): Get filtered results for ALL products"
            $splat['Status'] = @("Supported", "Unsupported")
            if ($name) {
                Write-Verbose "Get-PLMProduct(): Adding name to filter the results"
                $splat['Name'] = $name
            }
            Get-FilteredResults @splat
        }
    }
}

Function Get-PLMInterestingEvent {
    <#
    .SYNOPSIS
    A cmdlet to identify any product which has an interesting date between today
    (the day the cmdlet is run) and the days/date specified
    
    .DESCRIPTION
    A cmdlet to identify any product which has an interesting date between today
    (the day the cmdlet is run) and the days/date specified
    
    .PARAMETER Product
    Specifies the products to look through. Generally this is passed through the
    pipeline from Get-PLMProduct.
    
    .PARAMETER Days
    Specifies the number of days to offset from the current date. Each date
    field provided in the product will be checked to see if the date falls on
    or between the offset date and todays date.
    
    .PARAMETER Date
    Specifies the date to be used in the search to/from todays date. Each date
    field provided in the product will be checked to see if the date falls on
    or between the date specified and todays date.
    
    .EXAMPLE
    Get-PLMProduct | Get-PLMInterestingEvent -days 8 | FT

    Search through all products and return any which has a date that falls between todays date and 8 days in the future

    .EXAMPLE
    Get-PLMProduct | Get-PLMInterestingEvent -date $(Get-date -date "26/1/2022")

    Search through all products and return any which has a date that falls between todays date and 26th Jan 2022

    .EXAMPLE
    Get-PLMProduct | Get-PLMInterestingEvent -date $(Get-date -date "25/12/2019")

    Search through all products and return any which has a date that falls between todays date and 25th Dec 2019.
    Useful to see which products have recently had an event since a previous date.

    .EXAMPLE
    Get-PLMProduct | Get-PLMInterestingEvent -days -60

    Search through all products and return any which has a date that falls between todays date and 60 days in the past.
    Useful to see which products have recently had an event in the last x amount of days.

    .NOTES
    Author(s): Dale Coghlan
    Twitter: @DaleCoghlan
    Github: dcoghlan

    .LINK
    https://github.com/dcoghlan/VMware.LifecycleMatrix
    #>

    param (
        [Parameter (Mandatory = $True, ValueFromPipeline = $true, ParameterSetName = "InterestingDays")]
        [Parameter (Mandatory = $True, ValueFromPipeline = $true, ParameterSetName = "InterestingDate")]
        [ValidateNotNullorEmpty()]
        [object[]]$Product,
        [Parameter (Mandatory = $True, ParameterSetName = "InterestingDays")]
        [ValidateNotNullorEmpty()]
        [Int32]$Days,
        [Parameter (Mandatory = $True, ParameterSetName = "InterestingDate")]
        [ValidateNotNullorEmpty()]
        [datetime]$Date 
    )

    process {
        if ($PSCmdlet.ParameterSetName -eq 'InterestingDays') {
            $InterestingDate = $(Get-Date).AddDays($days)
        }
        elseif ($PSCmdlet.ParameterSetName -eq 'InterestingDate') {
            $InterestingDate = $Date
        }
        Get-InterestingResults -Results $Product -Date $InterestingDate
    }
}

_init