GitBones.psm1



class GitBonesTemplate {
   
    [string] 
    hidden $BonesURL = 'https://gitlab.com/GitBones/gitbones/raw/master/templates/templates.json'
    ## Name of the GitBones Template
    [string]
    $Name
    ## The HTTPS URL of the GitBones Template
    [string]
    $URL
    ## Short description of the repository
    [string]
    $Description
    ## The group that is managing the repository of the URL
    [string]
    $Maintainer
    ## The original Author of the repository
    [string]
    $Author
    ## The identified Git Host Server (e.g. GitLab, GitHub, Bitbucket)
    ## if repository is built off of specific Git Host service
    [string]
    $GitHost
    ## The specific CI tool being used for the repository
    [string]
    $CI
    ## The general topic of the template
    [string]
    $Topic
    ## The specific programming language or code-base of the repository
    [string]
    $Language
    ## Informs if the repository has submodules in it
    [bool]
    $Submodules

    GitBonesTemplate (
        [string]$Name,
        [string]$URL,
        [string]$Description,
        [string]$Maintainer,
        [string]$Author,
        [string]$GitHost,
        [string]$CI,
        [string]$Topic,
        [string]$Language,
        [bool]$Submodules
    )
    {
        $this.Name =$Name
        $this.URL = $URL
        $this.Description = $Description
        $this.Maintainer = $Maintainer
        $this.Author = $Author
        $this.GitHost = $GitHost
        $this.CI = $CI
        $this.Topic = $Topic
        $this.Language = $Language
        $this.Submodules = $Submodules
    }

    GitBonesTemplate([string]$Name) {
        $Response = invoke-restmethod -uri $this.BonesURL -Method Get
        $Result = $Response.Projects | Where-Object -Property Name -LIKE $Name
        $this.Name = $Result[0].Name
        $this.URL = $Result[0].URL
        $this.Description = $Result[0].Description
        $this.Maintainer = $Result[0].Maintainer
        $this.Author = $Result[0].Author
        $this.GitHost = $Result[0].GitHost
        $this.CI = $Result[0].CI
        $this.Topic = $Result[0].Topic
        $this.Language = $Result[0].Language
        $this.Submodules = $Result[0].Submodules
    }

    [void]CreateRepository([string]$NewGitRepoUri) {
        [string]$TemplateToClone = $this.URL
        Write-Host "Starting GitBones build for template $TemplateToClone"
        $RepositoryName = $($NewGitRepoUri.Split('/')[-1]) -replace ".git", ""
        if(test-path ./$RepositoryName -ErrorAction SilentlyContinue){
            Write-Error "Directory $RepositoryName already exists. Please ensure directory named $RepositoryName is not in the present working directory." -ErrorAction Stop
        }# end if
        Write-Host "Creating git project directory $RepositoryName in present working directory"
        $ParentDirectory = (Get-Item -Path ".\").FullName
        try{
            New-Item -ItemType Directory -Name $RepositoryName -ErrorAction Stop
            Write-Host "git project directory $RepositoryName created..."
            Write-Host "Starting copy of $TemplateToClone into $RepositoryName directory..."
            git clone $TemplateToClone ./$RepositoryName --quiet
            ## verify that no errors occurred during clone
            if(!($?)){
                Write-Error "git clone process failed for $TemplateToClone." -ErrorAction Stop
            }# end if
            ## verify that the clone created the directory and sanitize it
            if(test-path ./$RepositoryName/.git -ErrorAction SilentlyContinue){
                Write-Host "$TemplateToClone successfully copied"
                Write-Host "Sanitizing GitBones template..."
                Remove-Item -Path ./$RepositoryName/.git -Recurse -Force
            }
            else {
                ## there was some error with cloning and throw terminating error
                Write-Error "$TemplateToClone failed to copy" -ErrorAction Stop
            }
            Write-Host "Initializing new git project..."
            Set-Location $RepositoryName -ErrorAction Stop
            git init --quiet
            ## check to ensure git init worked
            if(!($?)){
                Write-Error "Git init failure" -ErrorAction Stop
            }# end if
            Write-Host "Setting repository $RepositoryName remote origin to $NewGitRepoUri"
            git remote add origin $NewGitRepoUri
            if(!($?)){
                Write-Error "Failed adding remote origin for git project" -ErrorAction Stop
            }# end if
            Set-Location $ParentDirectory
            Write-Host "GitBones successful created template $TemplateToClone for $RepositoryName." -ForegroundColor Green
        }
        catch {
            ## Failure to clone project, roll back all changes
            Set-Location $ParentDirectory
            ## if directory for project was created, delete it rolling back
            if(test-path ./$RepositoryName -ErrorAction SilentlyContinue){
                Remove-Item -Path ./$RepositoryName -Recurse -Force
            }
            Write-Error "$_" -ErrorAction Stop
        }# end try-catch
    }# end method
}# end class GitBonesTemplate

function Invoke-GitInstall {
    PARAM()
    BEGIN {
        [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12;
    }# end BEGIN
    PROCESS {
        if(-NOT(Test-GitApp)){
            ## heavily pulled from https://github.com/tomlarse/Install-Git/blob/master/Install-Git/Install-Git.ps1
            ## only run if on a Windows OS
            if (Test-WindowsOS){
                $gitExePath = "$env:ProgramFiles\Git\bin\git.exe"
                foreach ($asset in (Invoke-RestMethod https://api.github.com/repos/git-for-windows/git/releases/latest).assets) {
                    if ($asset.name -match 'Git-\d*\.\d*\.\d*.\d*-64-bit\.exe') {
                        $dlurl = $asset.browser_download_url
                    }
                }# end foreach
                
                if (!(Test-Path $gitExePath -ErrorAction SilentlyContinue)) {
                    Remove-Item -Force $env:TEMP\git-stable.exe -ErrorAction SilentlyContinue
                }# end if
                try{
                    Invoke-WebRequest -Uri $dlurl -OutFile $env:TEMP\git-stable.exe -ErrorAction Stop
                }
                catch{
                    Write-Error "Failed to download git.exe" -ErrorAction Stop
                }# end try-catch
                try {
                    Start-Process -Wait $env:TEMP\git-stable.exe -ArgumentList /silent -ErrorAction Stop
                    Write-Host "Installation complete!" -ForegroundColor Green
                }
                catch{
                    Write-Error "Failed to install git.exe" -ErrorAction Stop
                }# end try-catch
            }
            else{
                Write-Host "This script is currently only supported on the Windows operating system." 
                Write-Host "If running Linux, use packagement solution to install."
                Write-Host "If running MacOS, use brew and using Xcode command line tools run 'git --version' and you should be prompted to install."
            }
        }
        else {
            Write-Host "Git correctly installed"
        }
    }# end PROCESS
    END {}# end END
}# end Invoke-GitInstall
Export-ModuleMember -Function Invoke-GitInstall

function Get-GitBonesTemplate {
<#
.Synopsis
   Retrieves all supported GitBones Templates
.DESCRIPTION
   Retrieves a full or filtered list of GitBones Templates. The templates provded by GitBones
   are searchable by GitBones Template URI, Name, Topic, Author, or Language. The Git-GitBonesTemplate
   can use the simplified alias of BonesList to quickly query available templates. Use this function
   to discover GitBones templates to build your projects with quickstarts and skeleton directories
   available.
.EXAMPLE
   Get-GitBonesTemplate -GitBonesUri 'https://gitlab.com/git-templates/default.git'
.EXAMPLE
   BonesList -Bones 'https://gitlab.com/git-templates/default.git'
.EXAMPLE
   Get-GitBonesTemplate -Topic 'CloudFormation'
.EXAMPLE
   BonesList -Topic 'CloudFormation'
.EXAMPLE
   Get-GitBonesTemplate -Author 'AWS'
.EXAMPLE
   BonesList -Author 'AWS'
.EXAMPLE
   Get-GitBonesTemplate -Language 'PowerShell'
.EXAMPLE
   BonesList -Application 'PowerShell'
#>

    [CmdletBinding(SupportsShouldProcess=$true, 
        ConfirmImpact='Medium')]
    [Alias('BonesList')]
    Param(
        # The GitBones Template HTTPS URL hosted on GitBones
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [Alias("Bones")]
        [string]$GitBonesUri,
        # Filter results based on GitBones Name keyword or phrase
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=1)]
        [string]$Name,
        # Filter results based on GitBones Topic keyword or phrase
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=2)]
        [string]$Topic,
        # Filter results based on GitBones Author keyword or phrase
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=3)]
        [string]$Author,
        # Filter results based on GitBones programming Language keyword or phrase
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=4)]
        [string]$Language
    )# end params

    BEGIN {
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        Test-GitBonesConnection
        ## current location for the template master catalog
        $BonesURL = 'https://gitlab.com/GitBones/gitbones/raw/master/templates/templates.json'
        $Response = invoke-restmethod -uri $BonesURL -Method Get
    }# end BEGIN
    PROCESS{
        if($Response.Projects.Count -GE 1){
            $Result = $Response.Projects
            ## filter based on supplied URL
            if($GitBonesUri){
                $Result =  $Result | Where-Object -Property URL -LIKE $GitBonesUri
            }# end if GitBonesUri

            if($Name){
                $Result =  $Result | Where-Object -Property Name -LIKE "*$Name*"
            }# end if GitBonesUri

            ## filter based on supplied topic
            if($Topic){
                $Result =  $Result | Where-Object -Property Topic -LIKE "*$Topic*"
            }#enf if Topic

            ## filter based on supplied Author
            if($Author){
                $Result =  $Result | Where-Object -Property Author -LIKE "*$Author*"
            }# end if Author

            ## filter based on supplied programming language
            if($Language){
                $Result =  $Result | Where-Object -Property Language -LIKE "*$Language*"
            }#enf if application
        }
        else {
            Write-Error "GitBones connection is not returning queries at this time." -ErrorAction Stop 
        }# end else-if
    }# end PROCESS
    END{
        return $Result
    }# end END
}# end Get-GitBonesTemplate
Export-ModuleMember -Function Get-GitBonesTemplate -Alias BonesList

function New-GitBonesRepo {
<#
.Synopsis
   Creates a git repository skeleton directory based on GitBones or user templates.
.DESCRIPTION
   Creates a git project skeleton directory based on GitBones or user specified templates.
   Users are able to leverage the GitBones Template library via BoneList command or supply
   their own git project URI to clone their own git skeleton projects. Access to projects
   to GitBones is based on HTTPS and all projects are open to the public. User template access
   is reliant on the user having access to their specified templates through typical git
   clone process and supports both HTTPS and SSH. If using SSH or HTTPS, user will be
   prompted to authenticate if required. If access is denied, user should verify their
   permissions.
.EXAMPLE
   New-GitBonesRepo -GitRepoUri 'git@gitlab.com:rolston/test.git' `
    -GitBonesName 'GitLab-CloudFormation'
 
    Creates a project test in the present working directory using the GitBones
    Template GitLab-CloudFormation
.EXAMPLE
   BonesInit -GitRepoUri 'git@gitlab.com:rolston/test.git' `
    -GitBonesName 'GitLab-CloudFormation'
 
    Example of using alias BonesInit to create a project titled test in the
    present working directory using the GitBones Template GitLab-CloudFormation
    and with a target user Git Project using HTTPS as remote origin
.EXAMPLE
   BonesInit -GitRepoUri 'https://gitlab.com/rolston/test.git' `
    -Bones 'Gitlab-CloudFormation'
 
    Using simplified BonesInit alias and Bones parameter to create a project using a
    GitBones template GitLab-CloudFormation with a target user Git Project using
    HTTPS as remote origin.
.EXAMPLE
   BonesInit -GitRepoUri 'git@gitlab.com:rolston/test.git' `
    -UserTemplate 'git@gitlab.com:rolston/mytemplate.git'
     
    Using the user specified template, create a skeleton git project from the bones
    of the template "mytemplate.git"
.EXAMPLE
   New-GitBonesRepo -GitRepoUri 'git@gitlab.com:rolston/test.git' `
    -UserTemplate 'git@gitlab.com:rolston/mytemplate.git'
     
    Using the user specified template with SSH, create a skeleton git project from the bones
    of the template "mytemplate.git"
.EXAMPLE
   New-GitBonesRepo -GitRepoUri 'git@gitlab.com:rolston/test.git' `
    -UserTemplate 'https://gitlab.com/rolston/mytemplate.git'
     
    Using the user specified template with HTTPS, create a skeleton git project from the bones
    of the template "mytemplate.git"
#>

    [CmdletBinding(SupportsShouldProcess=$true,
                  DefaultParameterSetName='GitBones',
                  ConfirmImpact='Medium')]
    [Alias('BonesInit')]
    Param(
        # Git Project SSH or HTTPS URL for new project
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [ValidateNotNullOrEmpty()]
        [Alias("p")]
        [string]
        $GitRepoUri,

        # The GitBones Project Name
        [Parameter(Mandatory=$false,
                   ValueFromPipeline=$false,
                   Position=1,
                   ParameterSetName='GitBones')]
        [ValidateNotNullOrEmpty()]
        [Alias("Bones", "b")]
        [string]
        $GitBonesName,

        # The User Template Project URI
        [Parameter(Mandatory=$false,
                   ValueFromPipeline=$false,
                   Position=1,
                   ParameterSetName='UserTemplate')]
        [ValidateNotNullOrEmpty()]
        [string]
        $UserTemplateUri
    )

    BEGIN {
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        $GitBonesUri = 'https://gitlab.com/GitBones/gitbones/raw/master/templates/templates.json'
        if(!(Test-GitInstallation)){
            Write-Error "Missing git installation requirements" -ErrorAction Stop
        }# end if
        
    }# end BEGIN
    PROCESS{
        if($GitBonesName) {
            if ((Invoke-WebRequest -Uri $GitBonesUri -ErrorAction SilentlyContinue -UseBasicParsing).StatusCode -EQ 200) {
                Write-Verbose "connection succeeded to bones template $GitBonesUri"
            }
            else {
                Write-Verbose "connection failed to bones template $GitBonesUri"
                Write-Error "Bones Template network connection fail: $GitBonesUri" -ErrorAction Stop
            }
            [GitBonesTemplate]$GitBonesResult = [GitBonesTemplate]::new($GitBonesName)
            if($GitBonesResult.URL){
                $GitBonesResult.CreateRepository($GitRepoUri)
            }
            else {
                Write-Error "No Bones Project Template found. Check the name of the Bone Project Template" -ErrorAction Stop
            }
        }# end if GitBonesName

        if($UserTemplateURI){
            [GitBonesTemplate]$GitTemplate = [GitBonesTemplate]::new( `
                'Custom User Template', `
                $UserTemplateURI, `
                'Custom User Template', `
                $env:UserName, `
                $env:UserName, `
                'User defined', `
                'User defined', `
                'User defined', `
                'User defined', `
                $false )
            $GitTemplate.CreateRepository($GitRepoUri)
        }# end if UserTemplateURI
    }# end PROCESS
    END{

    }# end END
}# end New-GitBonesRepo
Export-ModuleMember -Function New-GitBonesRepo -Alias BonesInit


function Set-GitPath {
<#
.Synopsis
   Sets the path for Windows OS for Git.exe installations. Note this
   script requires an elevated session (runas admin).
.DESCRIPTION
   Sets the path for Windows OS Git.exe installations
   looking at $env:PATH checking for "$env:ProgramFiles\Git\cmd"
   to be in the path. This functions requires the session to be
   running under elevated privileges.
.EXAMPLE
   Set-Path
#>

    [CmdletBinding(SupportsShouldProcess=$true, 
                  ConfirmImpact='Medium')]
    PARAM()
    if (Test-WindowsOS){
        ## verify git is installed prior to setting path
        if(Test-GitApp){
            ## git is installed, test if path is set in system path
            if(!(Test-GitPath)){
                if(Test-IsAdmin){
                $GitPath = "$env:ProgramFiles\Git\cmd"
                [Environment]::SetEnvironmentVariable("Path", $env:Path + ";$GitPath", [EnvironmentVariableTarget]::Machine)
                }
                else {
                    Write-Warning "To set path you will need to run session as admin"
                    Write-Warning "Path not set for Git installation"
                }
            }
            else {
                Write-Host "Git Path already set"
            }
        }
        else
        {
            Write-Warning "Cannot set path until Git is installed. Please run Invoke-GitInstall"
        }
    }# end if Windows OS
    else {
        Write-Host "Set-GitPath is only applicable to Windows OS."
    }
}# end Set-GitPath
Export-ModuleMember -Function Set-GitPath

function Test-GitApp {
    [CmdletBinding(SupportsShouldProcess=$true, 
                  ConfirmImpact='Medium')]
    PARAM()
    # Tests the Installation of Git
    if (Test-WindowsOS){
        Write-Verbose "System identified as Windows OS."
        $GitPath = "$env:ProgramFiles\Git\cmd"
        if(!(Test-Path -Path "$GitPath\git.exe" -ErrorAction SilentlyContinue)){
            return $false
        }
        else {
            return $true
        }# end if
    }# end if IsWindows
    else {
        ## leverage command -v to see if git is installed on Linux or Mac
        if(!(Invoke-Command -scriptblock {command -v git} -ErrorAction SilentlyContinue)){
            return $false
        }
        else {
            return $true
        }
    }# end elseif Linux or Mac OS
}# end Test-GitApp

function Test-GitBonesConnection {
    [CmdletBinding(SupportsShouldProcess=$true, 
                  ConfirmImpact='Medium')]
    PARAM()
    $GitBonesUri = 'https://gitlab.com/GitBones/gitbones/raw/master/templates/templates.json'
    if ((Invoke-WebRequest -Uri $GitBonesUri -ErrorAction SilentlyContinue -UseBasicParsing).StatusCode -EQ 200) {
        write-verbose "connection succeeded to bones template $GitBonesUri"
    }
    else {
        write-verbose "connection failed to bones template $GitBonesUri"
        Write-Error "Bones Template network connection fail: $GitBonesUri" -ErrorAction Stop
    }
    
}# end Test-GitBonesConnection

function Test-GitPath {
    ## Only for Windows do we need to check
    [CmdletBinding(SupportsShouldProcess=$true, 
                  ConfirmImpact='Medium')]
    PARAM()

    ## run only on Windows OS
    if (Test-WindowsOS){
        $GitPath = "$env:ProgramFiles\Git\cmd"
        if(($env:Path).Contains($GitPath)){
            return $true
        }
        else {
            return $false
        }# end if env:Path
    }
    else {
        ## system in either Linux or Mac
        ## and path is not an issue
        return $true
    }# end if-else
}# end Test-GitPath

function Test-GitInstallation {
    BEGIN {}# end BEGIN
    PROCESS {
        if(!(Test-GitApp)) {
                Write-Warning "Git application not installed, please run Invoke-GitInstall to setup Git.exe"
                return $false  
        }# end if
           
        if(!(Test-GitPath)){
            if(Test-WindowOS){ 
                Write-Warning "Git application not found under env:PATH, please run Set-GitPath to complete Git configuration"
                return $false
            }
        }
        return $true
    }# end PROCESS
    END {}# end END
}# end Test-GitInstallation

function Test-IsAdmin {
    ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
}# end Test-IsAdmin

function Test-WindowsOS {
    if ((!($IsLinux) -AND (!($IsMacOS)))){
        return $true
    }
    else {
        return $false
    }# end if-else
}# end Test-WindowsOS