Public/Get-SolutionProjects.ps1

<#
.SYNOPSIS
    Recursively retrieve solution projects.
.DESCRIPTION
    This function navigates through the solution $dte object finding projects and recursively returning them.
.PARAMETER Object
    Represents a System.__ComObject from the $dte.
    The Object does not need to be set for this function to succeed.
    If this value is not set, this function will begin using $dte.Solution.Projects.
.EXAMPLE
    Get-SolutionProjects
.EXAMPLE
    Get-SolutionProjects -Verbose
.NOTES
    Author: Casey Crouse
    Created On: 07/22/2019
#>

Function Get-SolutionProjects() {
    [CmdletBinding()]
    param(
        [System.Object]$Object
    )

    if ($null -eq $dte) {
        Write-Error "$($MyInvocation.MyCommand) stopped - this command requires Visaul Studio."
        return $null
    }

    if ($null -eq $Object) {
        # Nothing was passed in, this is likely to be the first iteration.
        $Object = $dte.Solution.Projects

        # Create the new list for projects now
        $script:projectList = New-Object System.Collections.Generic.List[System.Object]
        Write-Verbose "Creating new list for the first time."
        Write-Verbose ""
    
        # The following ProjectNamesList is needed in order to figure out if a project has been unloaded.
        # When a project is unloaded, sometimes, all we have is the name.
        # We can check the name, if we have it, against the 'gold' list to ensure it's an actual project and not a virtual folder.
        Write-Verbose ""
        Write-Verbose "Building list of project files in order to get the project names 'gold' list."
        # "Script" scoped values will keep their values through the recursion.
        $script:SolutionDirectory = Split-Path -Path $(Get-SolutionFullName -Verbose:$VerbosePreference -ErrorAction $ErrorActionPreference)
        $script:ProjectNamesList = New-Object System.Collections.Generic.List[string] -Verbose:$VerbosePreference -ErrorAction $ErrorActionPreference
        $script:ProjectFileNames = Get-ChildItem -Path $script:SolutionDirectory -Recurse -Force -Include "*.*proj" -Verbose:$VerbosePreference -ErrorAction $ErrorActionPreference
    
        Write-Verbose "Adding the following names:"
        foreach ($name in $script:ProjectFileNames) {
            $withoutExt = [System.IO.Path]::GetFileNameWithoutExtension($name)
            Write-Verbose "`t$name"
            $script:ProjectNamesList.Add($withoutExt)
        }
        Write-Verbose ""
    }

    Write-Verbose "Project list count is: $($script:projectList.Count)"
    Write-Verbose ""
    
    # Get the entry object types
    $script:type = [Microsoft.VisualBasic.Information]::TypeName($Object)
    $script:typeObject = [Microsoft.VisualBasic.Information]::TypeName($Object.Object)
    $script:ProjectName = $Object.Name
    $script:ProjectFullName = $Object.FullName

    Write-Verbose "Project Name: $script:ProjectName"
    Write-Verbose "Project Full Name: $script:ProjectFullName"
    Write-Verbose "Project Object Type: $script:type"
    Write-Verbose "Project Object.Object Type: $script:typeObject"
    
    # Is the item coming in, a project? Check it's type
    if ($script:type -eq 'OAVSProject' -or $script:type -eq 'OAProject' -or $script:type -eq 'VSProject3') {
        Write-Verbose ""
        Write-Verbose "`tAdding to list: $($Object.Name)"
        $script:projectList.Add([System.Object]$Object)
        Write-Verbose ""
    # Is the item coming in, a project? Check it's object's type
    } elseif ($script:typeObject -eq 'OAVSProject' -or $script:typeObject -eq 'OAProject' -or $script:typeObject -eq 'VSProject3') {
        Write-Verbose ""
        Write-Verbose "`tAdding to list: $($Object.Name)"
        $script:projectList.Add([System.Object]$Object.Object)
        Write-Verbose ""

    # Projects is the root collection
    } elseif ($script:type -eq 'Projects') {
        Write-Verbose ""
        Write-Verbose "Entered IF BLOCK 'Projects'..."

        foreach ($project in $Object) {
            $local:type = [Microsoft.VisualBasic.Information]::TypeName($project)
            $local:typeObject = [Microsoft.VisualBasic.Information]::TypeName($project.Object)

            Write-Verbose ""
            Write-Verbose "`tWithin Projects - Name: $($project.Name)"
            Write-Verbose "`t`tEntry Object Type: $local:type"
            Write-Verbose "`t`tEntry Object.Object Type: $local:typeObject"

            # We're at the top level folders, each folder below is considered a 'project'
            if ($local:typeObject -eq 'SolutionFolder') {
                Write-Verbose "`t`tType is 'SolutionFolder' - Starting Recursion on $($project.Name)"

                # Recursion - Capturing return list to avoid bad output.
                # However, since the projectList variable scope is 'script',
                # the items have already been added to the list, no need to re-add them.
                $items = Get-SolutionProjects -Object $project.ProjectItems | Out-Null

            # We have a project at the top level of the solution
            } elseif ($local:type -eq 'OAVSProject' -or $local:type -eq 'OAProject' -or $local:type -match '^VSProject(?<Version>\d*)$') {
                Write-Verbose ""
                Write-Verbose "`tAdding to list: $($project.Name)"
                $script:projectList.Add([System.Object]$project)
                Write-Verbose ""
            } elseif ($local:typeObject -eq 'OAVSProject' -or $local:typeObject -eq 'OAProject' -or $local:typeObject -match '^VSProject(?<Version>\d*)$') {
                Write-Verbose ""
                Write-Verbose "`tAdding to list: $($project.Name)"
                $script:projectList.Add([System.Object]$project)
                Write-Verbose ""
            } elseif ($local:type -eq 'Project' -and $local:typeObject -eq 'Nothing') {
                if ($script:ProjectNamesList.Contains($project.Name)) {
                    # Found a project type that might be unloaded. Unable to check "UniqueName" here
                    # because only some of the project types have this property. Name, however is available.
                    # Consider checking the name in a list of project files that we can gather at the start.
                    Write-Verbose ""
                    Write-Verbose "`t`t$($project.Name) is unloaded."
                    Write-Verbose "`t`tAdding to list: $($project.Name)"
                    $script:projectList.Add([System.Object]$project)
                    Write-Verbose ""
                }
            } elseif ($local:type -eq 'Project' -and $local:typeObject -ne 'Nothing') {
                Write-Verbose "`tType is 'Project' - Starting Recursion on $($project.Name)"
                Write-Verbose "`t$([Microsoft.VisualBasic.Information]::TypeName($project.ProjectItems))"

                # Recursion - Capturing return list to avoid bad output.
                # However, since the projectList variable scope is 'script',
                # the items have already been added to the list, no need to re-add them.
                $items = Get-SolutionProjects -Object $project.ProjectItems | Out-Null

            }
            else {
                Write-Information "$($project.Name) unknown type: $local:typeObject"
            }
        }
    } elseif ($script:type -eq 'ProjectItems') {
        Write-Verbose ""
        Write-Verbose "Entered IF BLOCK 'ProjectItems'..."

        foreach ($project in $Object) {

            $local:type = [Microsoft.VisualBasic.Information]::TypeName($project)
            $local:typeObject = [Microsoft.VisualBasic.Information]::TypeName($project.Object)

            Write-Verbose ""
            Write-Verbose "`tWithin ProjectItems - Name: $($project.Name)"
            Write-Verbose "`t`tEntry Object Type: $local:type"
            Write-Verbose "`t`tEntry Object.Object Type: $local:typeObject"
            
            if ($local:type -eq 'ProjectItem' -and $local:typeObject -eq 'Nothing') {
                if ($script:ProjectNamesList.Contains($project.Name)) {
                    # Found a project type that might be unloaded. Unable to check "UniqueName" here
                    # because only some of the project types have this property. Name, however is available.
                    # Consider checking the name in a list of project files that we can gather at the start.
                    Write-Verbose ""
                    Write-Verbose "`t`t$($project.Name) is unloaded."
                    Write-Verbose "`t`tAdding to list: $($project.Name)"
                    $script:projectList.Add([System.Object]$project)
                    Write-Verbose ""
                }
            } elseif ($local:type -eq 'Project') {
                Write-Verbose "`tType is 'Project' - Starting Recursion on $($project.Name)"

                # Recursion - Capturing return list to avoid bad output.
                # However, since the projectList variable scope is 'script',
                # the items have already been added to the list, no need to re-add them.
                $items = Get-SolutionProjects -Object $project | Out-Null

            } elseif ($local:type -eq 'OAVSProject' -or $local:type -eq 'OAProject' -or $local:type -match '^VSProject(?<Version>\d*)$') {
                Write-Verbose ""
                Write-Verbose "`tAdding to list: $($project.Name)"
                $script:projectList.Add([System.Object]$project)
                Write-Verbose ""
            } elseif ($local:typeObject -eq 'OAVSProject' -or $local:typeObject -eq 'OAProject' -or $local:typeObject -match '^VSProject(?<Version>\d*)$') {
                Write-Verbose ""
                Write-Verbose "`tAdding to list: $($project.Name)"
                $script:projectList.Add([System.Object]$project.Object)
                Write-Verbose ""
            } elseif ($local:type -eq 'ProjectItem' -and $local:typeObject -ne 'Nothing') {
                Write-Verbose "`tType is 'ProjectItem' - Starting Recursion on $($project.Name)"

                # Recursion - Capturing return list to avoid bad output.
                # However, since the projectList variable scope is 'script',
                # the items have already been added to the list, no need to re-add them.
                $items = Get-SolutionProjects -Object $project | Out-Null

            } else {
                Write-Information "$($project.Name) unknown type: $local:type"
            }
        }
    } elseif ($script:type -eq 'ProjectItem') {

        Write-Verbose ""
        Write-Verbose "Entered IF BLOCK 'ProjectItem'..."
        
        foreach ($projectItem in $Object.Object.ProjectItems) {

            $local:type = [Microsoft.VisualBasic.Information]::TypeName($projectItem)
            $local:typeObject = [Microsoft.VisualBasic.Information]::TypeName($projectItem.Object)

            Write-Verbose ""
            Write-Verbose "`tWithin ProjectItem - Name: $($projectItem.Name)"
            Write-Verbose "`t`tEntry Object Type: $local:type"
            Write-Verbose "`t`tEntry Object.Object Type: $local:typeObject"
            
            if ($local:type -eq 'ProjectItem' -and $local:typeObject -eq 'Nothing') {
                if ($script:ProjectNamesList.Contains($projectItem.Name)) {
                    # Found a project type that might be unloaded. Unable to check "UniqueName" here
                    # because only some of the project types have this property. Name, however is available.
                    # Consider checking the name in a list of project files that we can gather at the start.
                    Write-Verbose ""
                    Write-Verbose "`t`t$($projectItem.Name) is unloaded."
                    Write-Verbose "`t`tAdding to list: $($projectItem.Name)"
                    $script:projectList.Add([System.Object]$projectItem)
                    Write-Verbose ""
                }
            } elseif ($local:typeObject -eq 'Project') {
                Write-Verbose "`t`tType is 'Project' - Starting Recursion on $($projectItem.Name)"

                # Recursion - Capturing return list to avoid bad output.
                # However, since the projectList variable scope is 'script',
                # the items have already been added to the list, no need to re-add them.
                $items = Get-SolutionProjects -Object $projectItem | Out-Null

            } elseif ($local:type -eq 'OAVSProject' -or $local:type -eq 'OAProject' -or $local:type -match '^VSProject(?<Version>\d*)$') {
                Write-Verbose ""
                Write-Verbose "`tAdding to list: $($projectItem.Name)"
                $script:projectList.Add([System.Object]$projectItem)
                Write-Verbose ""
            } elseif ($local:typeObject -eq 'OAVSProject' -or $local:typeObject -eq 'OAProject' -or $local:typeObject -match '^VSProject(?<Version>\d*)$') {
                Write-Verbose ""
                Write-Verbose "`tAdding to list: $($projectItem.Name)"
                $script:projectList.Add([System.Object]$projectItem.Object)
                Write-Verbose ""
            } elseif ($local:typeObject -eq 'ProjectItem') {
                Write-Verbose "`tType is 'ProjectItem' - Starting Recursion on $($projectItem.Name)"

                # Recursion - Capturing return list to avoid bad output.
                # However, since the projectList variable scope is 'script',
                # the items have already been added to the list, no need to re-add them.
                $items = Get-SolutionProjects -Object $projectItem | Out-Null

            } else {
                Write-Information "$($Object.Name) unknown type: $local:typeObject"
            }
        }
    }
    
    Write-Verbose "List new count is...$($script:projectList.Count)"

    return $script:projectList
}