function Invoke-CategorizeCSProj { [CmdletBinding()] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Path')] [ValidateNotNullOrEmpty()] [string]$Path, [Parameter(Mandatory = $true, ParameterSetName = 'Project')] [ValidateNotNullOrEmpty()] [object]$Project ) $logLead = Get-LogLeadName $projectName = $Project.Name $deprecatedProjectRegex = "^(zz|deprecated|archive|obsolete|xxx)" # This is a magic value invented by Cole for max-length of file for discrepancy on what exists $magicChocoFilesLengthValue = 160 if ($PSCmdlet.ParameterSetName -eq 'Project') { if ([string]::IsNullOrWhiteSpace($Project.Path)) { Write-Warning "$logLead : Could not find a path property for [$($Project.Name) ($($Project.Type))]$(if ($ErrorActionPreference -ne 'Stop') { ", returning `$null"})" return $null } $Path = $Project.Path } if (Test-Path -Path $Path) { $Path = Resolve-Path -Path $Path } else { Write-Error "$logLead : Could not resolve the path [$Path].$(if ($ErrorActionPreference -ne 'Stop') { " Returning `$null" })" return $null } Write-Verbose "$logLead : Processing child objects for csproj files" $item = Get-Item -Path $Path if ($item.PSIsContainer) { $csprojList = Get-ChildItem -Path $Path -ChildPath "*.csproj" if (-not (Any $csprojList)) { Write-Error "$logLead : Provided path was a Directory, and no child projects were found in the folder directly.$(if ($ErrorActionPreference -ne 'Stop') { " Returning `$null" })" } $Path = $csprojList[0].FullName Write-Warning "$logLead : Provided path was a Directory, using the first found csproj in this folder, with path [$Path]" } if ($PSCmdlet.ParameterSetName -eq 'Path') { $projectName = [System.IO.Path]::GetFileNameWithoutExtension($Path) } $baseFolder = Split-Path -Path $Path -Parent Write-Host "$logLead : Begin processing project $($projectName)" $csproj = @{ Name = $projectName Path = $Path ChocoFiles = @{ HasChocoFiles = $false InstallFileSize = 0 InstallFileNew = $false UninstallFileSize = 0 UninstallFileNew = $false ChocolateyInstall = $null ChocolateyUninstall = $null } Nuspec = @{ HasNuspecFile = $false IsChocolatey = $false PackageId = $null Version = $null Raw = $null } AlkamiManifest = @{ HasAlkamiManifest = $false ComponentType = $null AlkamiManifest = $null IsLegacyInstaller = $false IsUnkownInstaller = $false } HasTestReferences = $false HasProgramMain = $false Packages = $null IsLikelyDeprecated = $Path -match $deprecatedProjectRegex TargetFramework = 'Framework' TargetFrameworkValue = $null HasPostBuildEvent = $false HasPreBuildEvent = $false } $rawCsproj = [xml](Get-Content -Path $Path -Raw) $files = Get-ChildItem -Path $baseFolder if (![string]::IsNullOrWhiteSpace($rawCsproj.Project.PropertyGroup.TargetFramework)) { $csproj.TargetFrameworkValue = $rawCsproj.Project.PropertyGroup.TargetFramework } elseif (![string]::IsNullOrWhiteSpace($rawCsproj.Project.PropertyGroup.TargetFrameworkVersion)) { $csproj.TargetFrameworkValue = $rawCsproj.Project.PropertyGroup.TargetFrameworkVersion } else { $csproj.TargetFrameworkValue = 'Unknown' } if ($rawCsproj.Project.PropertyGroup.TargetFramework -match 'net6' -or $rawCsproj.Project.PropertyGroup.TargetFramework -match 'netcore') { $csproj.TargetFramework = 'core' } elseif ($rawCsproj.Project.PropertyGroup.TargetFramework -match 'netstandard') { $csproj.TargetFramework = 'netstandard' } #region check for build events if ($null -ne $rawCsproj.Project.PropertyGroup.PreBuildEvent) { $csproj.HasPreBuildEvent = $true } if ($null -ne $rawCsproj.Project.PropertyGroup.PostBuildEvent) { $csproj.HasPostBuildEvent = $true } #endregion check for build events $includePackagesConfig = ($rawCsproj.Project.ItemGroup.None.Include -eq 'packages.config') -or ($rawCsproj.Project.ItemGroup.Content.Include -eq 'packages.config') $csproj.IncludesPackagesConfig = $includePackagesConfig Write-Verbose "$logLead : Read packages.config" $packages = @() $packagesConfigPath = Join-Path -Path $baseFolder -ChildPath "packages.config" $baseFolderContainsPackagesConfig = Test-Path -Path $packagesConfigPath if ($baseFolderContainsPackagesConfig) { $csproj.PackagesConfigPath = $packagesConfigPath $packagesConfig = [xml](Get-Content -Path $packagesConfigPath -Raw) foreach ($package in $packagesConfig.packages.package) { $packages += @{ PackageId = $package.id; Version = $package.version} } } foreach ($package in $rawCsproj.Project.ItemGroup.PackageReference) { if ([string]::IsNullOrWhiteSpace($package.Include)) { continue } $packages += @{ PackageId = $package.include; Version = "$($package.version)".Trim() } } $csproj.Packages = $packages Write-Verbose "$logLead : Categorize semver" # TODO: Recurse if not found on the base to look for tools\sem.ver a-la SDK widgets # Record the path if not found on the project root $semVerPath = $files.Where({$_.Name -eq "sem.ver"}).FullName if (![string]::IsNullOrWhiteSpace($semVerPath)) { if (Test-Path -Path $semVerPath) { $semverValueRaw = $null $semverValueVersion = $null try { $semverValueRaw = (ConvertFrom-Json (Get-Content -Path $semVerPath -Raw)) if (($null -ne $semverValueRaw) -and ($null -ne $semverValueRaw.Version)) { $semverValueVersion = "$($semverValueRaw.Version.Major).$($semverValueRaw.Version.Minor).$($semverValueRaw.Version.Patch)" } $csproj.SemVer = @{ Version = $semverValueVersion Raw = $semverValueRaw } } catch { Write-Warning "$logLead : Could not capture the semver from [$semverPath]" } } } #region look for specific nuget packages Write-Verbose "$logLead : Categorize nuget packages" $newRelicAgentApiPackage = $packages.Where({ $_.PackageId.StartsWith('NewRelic.Agent.Api', [System.StringComparison]::InvariantCultureIgnoreCase) }) if (Any $newRelicAgentApiPackage) { $csproj.NewRelicAgentApi = @{ Version = @($newRelicAgentApiPackage)[0].Version } } $msCore = $packages.Where({ $_.PackageId.Equals('Alkami.MicroServices.Core', [System.StringComparison]::InvariantCultureIgnoreCase) }) if (Any $msCore) { $csproj.MicroservicesCore = @{ Version = @($msCore)[0].Version } } $fluentMigratorPackage = $packages.Where({ $_.PackageId.StartsWith('FluentMigrator', [System.StringComparison]::InvariantCultureIgnoreCase) }) if (Any $fluentMigratorPackage) { $csproj.FluentMigrator = @{ # only take the first one found, because there could be .Runner, .Tools, etc Version = @($fluentMigratorPackage)[0].Version } } $topshelfPackage = $packages.Where({ $_.PackageId.StartsWith('TopShelf', [System.StringComparison]::InvariantCultureIgnoreCase) }) if (Any $topshelfPackage) { $csproj.TopShelf = @{ Version = @($topshelfPackage.Version)[0].Version } } # Ends in .Tests? foreach ($testFrameworkPrefix in @('NUnit', 'XUnit', 'MSTest')) { $projectHasTestReferences = $packages.Where({ $_.PackageId.StartsWith($testFrameworkPrefix, [System.StringComparison]::InvariantCultureIgnoreCase) }) $csproj.HasTestReferences = Any $projectHasTestReferences if ($csproj.HasTestReferences) { break } } #endregion look for specific nuget packages #region look for manifest details Write-Verbose "$logLead : Categorize Manifest" $projectUsesLegacyServiceInstaller = $packages.Where({ $_.PackageId.StartsWith('Alkami.MicroServices.Installer', [System.StringComparison]::InvariantCultureIgnoreCase) }) if (Any $projectUsesLegacyServiceInstaller) { $csproj.AlkamiManifest.ComponentType = "Service" $csproj.AlkamiManifest.HasAlkamiManifest = $false $csproj.ServiceInstaller = @{ Name = $projectUsesLegacyServiceInstaller.PackageId Version = $projectUsesLegacyServiceInstaller.Version IsMaster = $projectUsesLegacyServiceInstaller.PackageId -match 'Master' Legacy = $true } $csproj.AlkamiManifest.IsLegacyInstaller = $true } # cover the case of .json or .yaml # Take the first one we find $projectHasAlkamiManifestPath = @($files.Where({ $_.Name.StartsWith("AlkamiManifest") -and ($_.Name.ToLower().IndexOf("explain") -eq -1) }).FullName)[0] $projectHasAlkamiManifest = ![string]::IsNullOrWhiteSpace($projectHasAlkamiManifestPath) if ($projectHasAlkamiManifest) { try { $packageManifest = (Get-PackageManifest -Path $projectHasAlkamiManifestPath) $csproj.AlkamiManifest.ComponentType = $packageManifest.general.componentType $csproj.AlkamiManifest.AlkamiManifest = $packageManifest $csproj.AlkamiManifest.HasAlkamiManifest = $true } catch { Write-Verbose "Something failed to process on [$projectHasAlkamiManifestPath]" } } else { if (!$projectUsesLegacyServiceInstaller) { $componentType = '' # try to determine what type of project it should be # TODO: Shorten the lookup by consolidating left of pipe $providers = Get-ChildItem -Path (Join-Path -Path $baseFolder -ChildPath "*.cs") -Recurse | Select-String -Pattern "public class .+:\s+(ConnectorBase|ProviderBase)\b" | Select-Object -Property Path $webExtensions = Get-ChildItem -Path (Join-Path -Path $baseFolder -ChildPath "*.cs") -Recurse | Select-String -Pattern "public class .+:\s+(IAlkamiWebExtension|IAlkamiModule)\b" | Select-Object -Property Path $widgets = Get-ChildItem -Path (Join-Path -Path $baseFolder -ChildPath "*.cs") -Recurse | Select-String -Pattern "public class .+:\s+(.+WidgetDescription|.+AppRegistration)\b" | Select-Object -Property Path if ($null -ne $providers) { $componentType = 'Provider' } if ($null -ne $widgets) { $componentType = 'Widget' } if ($null -ne $webExtensions) { $componentType = 'WebExtension' } if ([string]::IsNullOrWhiteSpace($componentType)) { $csProj.HasProgramMain = Get-ChildItem -Path (Join-Path -Path $baseFolder -ChildPath "*.cs") -Recurse | Select-String -Pattern "(public|private)\s?.*\s+Main\s*\(" | Select-Object -Property Path } $csproj.AlkamiManifest.ComponentType = $componentType $csproj.AlkamiManifest.IsUnkownInstaller = $true } } $csProj.ServicePointManagerLines = Get-ChildItem -Path (Join-Path -Path $baseFolder -ChildPath "*.cs") -Recurse | Select-String -Pattern "ServicePointManager" | Where-Object { -not $_.Line.Trim().StartsWith("//") } | ForEach-Object { "$($_.Path):$($_.LineNumber) :: $($_.Line)" } Write-Verbose "$logLead : Categorize tools" $toolsPath = Join-Path -Path $baseFolder -ChildPath "Tools" if (Test-Path -Path $toolsPath) { $csproj.Tools = @{ Files = (Get-ChildItem -Path $toolsPath -File).Name } if ($csproj.Tools.Files -match 'choco.*\.ps1') { $csproj.ChocoFiles.HasChocoFiles = $true } $chocolateyInstallPath = Join-Path -Path $toolsPath -ChildPath "chocolateyInstall.ps1" $chocolateyUninstallPath = Join-Path -Path $toolsPath -ChildPath "chocolateyUninstall.ps1" if (Test-Path -Path $chocolateyInstallPath) { $csproj.ChocoFiles.InstallFileSize = (Get-Item -Path $chocolateyInstallPath).Length if ((Get-Item -Path $chocolateyInstallPath).Length -lt $magicChocoFilesLengthValue) { $csproj.ChocoFiles.InstallFileNew = $true } } if (Test-Path -Path $chocolateyUninstallPath) { $csproj.ChocoFiles.UninstallFileSize = (Get-Item -Path $chocolateyUninstallPath).Length if ($csproj.ChocoFiles.UninstallFileSize -lt $magicChocoFilesLengthValue) { $csproj.ChocoFiles.UninstallFileNew = $true } } } #endregion look for manifest details # above checks tools folder, this checks root folder, solution level folder check is elsewhere $chocolateyInstallPath = Join-Path -Path $baseFolder -ChildPath "chocolateyInstall.ps1" $chocolateyUninstallPath = Join-Path -Path $baseFolder -ChildPath "chocolateyUninstall.ps1" if (Test-Path -Path $chocolateyInstallPath) { $csproj.ChocoFiles.InstallFileSize = (Get-Item -Path $chocolateyInstallPath).Length if ((Get-Item -Path $chocolateyInstallPath).Length -lt $magicChocoFilesLengthValue) { $csproj.ChocoFiles.InstallFileNew = $true } } if (Test-Path -Path $chocolateyUninstallPath) { $csproj.ChocoFiles.UninstallFileSize = (Get-Item -Path $chocolateyUninstallPath).Length if ($csproj.ChocoFiles.UninstallFileSize -lt $magicChocoFilesLengthValue) { $csproj.ChocoFiles.UninstallFileNew = $true } } $csproj.ShouldHaveAManifestedInstaller = ( ($csproj.Nuspec.IsChocolatey -and -not $csproj.AlkamiManifest.HasAlkamiManifest) -or $true -eq $csproj.ServiceInstaller.Legacy -or $csproj.AlkamiManifest.IsLegacyInstaller ) # TODO: Tools folder nuspec? $nuspecPath = @($files.Where({$_.Name.EndsWith(".nuspec")}).FullName)[0] if (![string]::IsNullOrWhiteSpace($nuspecPath)) { $nuspecContent = [xml](Get-Content $nuspecPath) $projectNuspecId = $nuspecContent.package.metadata.id $projectNuspecVersion = $nuspecContent.package.metadata.version # If it doesn't have a chocolatey include, it's a problem $nuspecIsChocolatey = ($nuspecContent.package.files.file.src.EndsWith('chocolateyInstall.ps1').where({$_}).Count -gt 0) $csproj.Nuspec.HasNuspecFile = $true $csproj.Nuspec.IsChocolatey = $nuspecIsChocolatey $csproj.Nuspec.PackageId = $projectNuspecId $csproj.Nuspec.Version = $projectNuspecVersion $csproj.Nuspec.Raw = $nuspecContent } return (New-Object -TypeName PSCustomObject -Property $csproj) }