2

I've tried to create a visual studio solution with nested solution folders through Powershell (envdte). Everything works up to 1 level deep, but nesting a solution folder doesn't seem to work (interface is null). The issue is described in this SO question:

Creating a tree of Solution Folders using DTE and Package Manager Console

Unfortunately that question hasn't been answered yet. I've reachted out to the poster of that question but he had taken another route within his solution so the question is still open.

An excerpt of my code. The Find-Solution method finds the solutionfolder I'm looking for within the given context (= startfolder). When found, it returns this item:

function Find-SolutionFolder {
    Param($SearchedFolderName, $StartFolder)

    Write-Output "number " $StartFolder.ProjectItems.Count
    For ($i = 1; $i -le $StartFolder.ProjectItems.Count; $i++) {
        $Item = $StartFolder.ProjectItems.Item($i)

        if ($Null -Eq $Item.Object) {
            continue;
        }

        if ($Item.Object.Kind -eq [EnvDTE80.ProjectKinds]::vsProjectKindSolutionFolder) {
            if ($Item.Name -Eq $SearchedFolderName) {
                return $Item
            }
            Find-SolutionFolder $SearchedFolderName $Item
        }
    }
}

The Add-Projects method takes care of saving the structure to the solution. The structure is:

  • Solution
  • ModuleTypeFolder (ie. Foundation)
  • ModuleGroupFolder (optional)
  • Folder for project
  • projectfiles

This is a nested structure. Everything works without the ModuleGroupFolder but when the structure has the ModuleGroupFolder it causes the error due to the null result of the Get-Interface. I've confirmed that the correct solution folder is found. It's just the variable $moduleGroupNameFolderInterface is null. The parameter modulePath is the path on disk

function Add-Projects {
    Param(
        [Parameter(Position = 0, Mandatory = $True)]
        [string]$ModulePath
    )

    Write-Output "Adding project(s)..."

    # For the sake of the example always use a folder named 'Foundation'
    $moduleTypeFolder = Get-FoundationSolutionFolder

    # When the literal 'Foundation' solution folder does not exist in the solution it will be created.
    if (-Not $moduleTypeFolder) {
        $dte.Solution.AddSolutionFolder($config.ModuleType)
        $moduleTypeFolder = Get-ModuleTypeSolutionFolder
    }
    $moduleTypeFolderInterface = Get-Interface $moduleTypeFolder.Object ([EnvDTE80.SolutionFolder])

    # Add ModuleGroup Folder if needed
    if (-Not [string]::IsNullOrWhiteSpace($config.ModuleGroupName)) {
        $moduleGroupNameFolder = Find-SolutionFolder $config.ModuleGroupName $moduleTypeFolder
        if (-Not $moduleGroupNameFolder) {
            $moduleTypeFolderInterface.AddSolutionFolder($config.ModuleGroupName)
            $moduleGroupNameFolder = Find-SolutionFolder $config.ModuleGroupName $moduleTypeFolder
        }
        $moduleGroupNameFolderInterface = Get-Interface $moduleGroupNameFolder.Object ([EnvDTE80.SolutionFolder])
        if ($Null -eq $moduleGroupNameFolderInterface) {
            Write-Output "moduleGroupNameFolderInterface is null; this is wrong"
        } else {
            $moduleNameFolder = $moduleGroupNameFolderInterface.AddSolutionFolder($config.ModuleName)
            $moduleNameFolderInterface = Get-Interface $moduleNameFolder.SubProject ([EnvDTE80.SolutionFolder])
            # Search in the new module folder for csproj files and add those to the solution.
            Get-ChildItem -File -Path $ModulePath -Filter "*$csprojExtension" -Recurse | ForEach-Object { $moduleNameFolderInterface.AddFromFile("$($_.FullName)")}
        }
    } else {
        $moduleNameFolder = $moduleTypeFolderInterface.AddSolutionFolder($config.ModuleName)
        $moduleNameFolderInterface = Get-Interface $moduleNameFolder.Object ([EnvDTE80.SolutionFolder])
        # Search in the new module folder for csproj files and add those to the solution.
        Get-ChildItem -File -Path $ModulePath -Filter "*$csprojExtension" -Recurse | ForEach-Object { $moduleNameFolderInterface.AddFromFile("$($_.FullName)")}
    }

    Write-Output "Saving solution..."
    $dte.Solution.SaveAs($dte.Solution.FullName)
}

Note. the example is not optimized (ie. duplicate code)

Can anybody help me solve the issue.

Update - answer to question I finally figured it out. Apparently when finding a nested solution folder where property Kind has guid {66A26722-8FB5-11D2-AA7E-00C04F688DDE} it's not the correct object yet. You have to use the object within the found item.

esther
  • 51
  • 6

1 Answers1

0

So basically you are looking for recursion. You can recurse like this.

For the solutions folder:

function RecurseSolutionFolderProjects(){
    param($solutionFolder = $(throw "Please specify a solutionFolder"))
    $projectList = @()
    for($i = 1; $i -le $solutionFolder.ProjectItems.Count; $i++){
        $subProject = $solutionFolder.ProjectItems.Item($i).subProject
        if($subProject -eq $null){
            continue;
        }

        if($subProject.Kind -eq [EnvDTE80.ProjectKinds]::vsProjectKindSolutionFolder)
        {
            $projectList += RecurseSolutionFolderProjects($subProject)
        } else {
            $projectList += $subProject
        }
    }
    return $projectList
}

For Project Files:

function GetProjectFiles(){
    param($project = $(throw "Please specify a project"))

    write-debug ("getting project files for " + $project.Name + " "+ $project.ProjectName)

    $projectItems = RecurseDescendants($project.ProjectItems)
        return $projectItems | Where-Object {$_.Kind -ne [EnvDTE.Constants]::vsProjectItemKindPhysicalFolder}
    }

For Project Items:

function GetProjectItems(){ 
    param($project = $(throw "Please specify a project"))
    if($project.ProjectItems.count -gt 0){
        write-debug "getting project items for '$project.Name' '$project.ProjectName'"
    }
    #example: GetProjectItems((GetSolutionProjects).get_Item(1))
    $result =RecurseDescendants($project.ProjectItems)
    return $result
}

Refer the Solution Hierarchy answer where the above functions are neatly explained

You can get the latest version from this GitHub Link

Hope it helps.

Ranadip Dutta
  • 8,857
  • 3
  • 29
  • 45
  • thank you for your reply. I don't quite understand how this will solve that casting the found solution folder (with Get-Interface) will not be null. I can find the solution folder but can't cast it to the correct type and use AddSolutionFolder on it. Does your solution address this issue? – esther Aug 06 '18 at 06:17
  • Not really. You need to explicitly cast it then. I have not seen your structure so tough to tell in that way. – Ranadip Dutta Aug 06 '18 at 08:35