Pagootle.psm1

#Region '.\private\Compare-SecureString.ps1' -1

function Compare-SecureString {
    <#
    .NOTES
    From https://stackoverflow.com/a/48810852
    #>

    param(
      [Security.SecureString]
      $secureString1,
  
      [Security.SecureString]
      $secureString2
    )
    try {
      $bstr1 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureString1)
      $bstr2 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureString2)
      $length1 = [Runtime.InteropServices.Marshal]::ReadInt32($bstr1,-4)
      $length2 = [Runtime.InteropServices.Marshal]::ReadInt32($bstr2,-4)
      if ( $length1 -ne $length2 ) {
        return $false
      }
      for ( $i = 0; $i -lt $length1; ++$i ) {
        $b1 = [Runtime.InteropServices.Marshal]::ReadByte($bstr1,$i)
        $b2 = [Runtime.InteropServices.Marshal]::ReadByte($bstr2,$i)
        if ( $b1 -ne $b2 ) {
          return $false
        }
      }
      return $true
    }
    finally {
      if ( $bstr1 -ne [IntPtr]::Zero ) {
        [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr1)
      }
      if ( $bstr2 -ne [IntPtr]::Zero ) {
        [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr2)
      }
    }
  }
#EndRegion '.\private\Compare-SecureString.ps1' 39
#Region '.\private\ConvertTo-XmlString.ps1' -1

function ConvertTo-XmlString {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory)]
        [String[]]
        $Group
    )

    process {
        $groupsXml = New-Object System.Xml.XmlDocument
        $root = $groupsXml.CreateElement("groups")
        $null = $groupsXml.AppendChild($root)

        foreach ($g in $group) {
            $groupElement = $groupsXml.CreateElement("group")
    
            $groupNameElement = $groupsXml.CreateElement("GroupName")
            $groupNameElement.InnerText = $g
            $null = $groupElement.AppendChild($groupNameElement)

            $null = $root.AppendChild($groupElement)
        }

        $groupsXml.OuterXml

    }
}
#EndRegion '.\private\ConvertTo-XmlString.ps1' 28
#Region '.\private\CopyMaxBytes.ps1' -1

function CopyMaxBytes {
    [CmdletBinding()]
    param(
        [Parameter()]
        [System.IO.FileStream]   
        $source,
    
        [Parameter()]
        [System.IO.Stream]
        $target,
    
        [Parameter()]
        [Int]
        $maxBytes,

        [Parameter()]
        [Int]
        $startOffset, 
    
        [Parameter()]
        [Int]
        $totalSize
    )
    end {
        $buffer = [byte[]]::CreateInstance([byte], 32767)
        $totalBytesRead = 0
        while ($true) {
            $bytesRead = $source.Read($buffer, 0, [Math]::Min($maxBytes - $totalBytesRead, $buffer.Length))
            if (!$bytesRead) { break }
            $target.Write($buffer, 0, $bytesRead)
            $totalBytesRead += $bytesRead
            if ($totalBytesRead -ge $maxBytes) { break }
            $overallProgress = $startOffset + $totalBytesRead
            Write-Progress -Activity "Uploading $fileName..." -Status "$overallProgress/$totalSize" -PercentComplete ($overallProgress / $totalSize * 100)
        }
    }
}
#EndRegion '.\private\CopyMaxBytes.ps1' 38
#Region '.\private\Get-ProGetConnectionString.ps1' -1

function Get-ProGetConnectionString {
    [CmdletBinding()]
    Param(
        [Parameter()]
        [ValidateScript({ Test-Path $_ })]
        [String]
        $ConfigFile = "C:\ProgramData\Inedo\SharedConfig\ProGet.config"
    )
    
    end {
        [xml]$xml = Get-Content $ConfigFile

        $xml.InedoAppConfig.ConnectionString
    }
}
#EndRegion '.\private\Get-ProGetConnectionString.ps1' 16
#Region '.\private\Invoke-CreateGroupStoredProc.ps1' -1

function Invoke-CreateGroupStoredProc {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory)]
        [String]
        $Name
    )

    end {

        $connectionString = Get-ProGetConnectionString

        # Define the stored procedure and parameters
        $storedProc = "dbo.Users_CreateGroup"
        $groupName = $Name

        # Create and open SQL connection
        $sqlConnection = New-Object System.Data.SqlClient.SqlConnection($connectionString)
        $sqlConnection.Open()

        # Create SqlCommand and specify it as a stored procedure
        $sqlCommand = $sqlConnection.CreateCommand()
        $sqlCommand.CommandText = $storedProc
        $sqlCommand.CommandType = [System.Data.CommandType]::StoredProcedure

        # Add parameter
        $sqlCommand.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@Group_Name", [System.Data.SqlDbType]::NVarChar, 50))).Value = $groupName

        # Execute the SqlCommand
        $sqlCommand.ExecuteNonQuery()

        # Close the connection
        $sqlConnection.Close()

        Write-Host "Group added successfully to ProGet."

    }
}
#EndRegion '.\private\Invoke-CreateGroupStoredProc.ps1' 39
#Region '.\private\Invoke-GetTaskStoredProc.ps1' -1

function Invoke-GetTaskStoredProc {
    [CmdletBinding()]
    Param()

    end {
        # Execute locally
        Add-Type -AssemblyName "System.Data"
        $StoredProcedure = 'dbo.Security_GetTasks'
        $ConnectionString = Get-ProGetConnectionString

        $connection = [System.Data.SqlClient.SqlConnection]::new($ConnectionString)
        $connection.Open()

        try {
            $command = $connection.CreateCommand()
            $command.CommandType = [System.Data.CommandType]::StoredProcedure
            $command.CommandText = $StoredProcedure

            $reader = $command.ExecuteReader()
            $results = [System.Collections.Generic.List[pscustomobject]]::new()

            while ($reader.Read()) {
                $row = @{}
                for ($i = 0; $i -lt $reader.FieldCount; $i++) {
                    $column = $reader.GetName($i)
                    $row[$column] = $reader.GetValue($i)
                }
                $results.Add([PSCustomObject]$row)
            }
            $reader.Close()

            $results
        }
        catch {
            Write-Error "An error occurred: $_"
        }
        finally {
            $connection.Close()
        }
    }
}
#EndRegion '.\private\Invoke-GetTaskStoredProc.ps1' 42
#Region '.\private\Invoke-GetUserStoredProc.ps1' -1

function Invoke-GetUserStoredProc {
    <#
        .SYNOPSIS
       Private function that executes dbo.Users_GetUsers stored procedure in ProGet Database
    #>

    [CmdletBinding()]
    Param(
        [Parameter()]
        [String]
        $Username
    )

    end {
                # Execute locally
                Add-Type -AssemblyName "System.Data"
                $StoredProcedure = 'dbo.Users_GetUsers'
                $ConnectionString = 'Server=Localhost\SQLEXPRESS;Database=ProGet;Trusted_Connection=true;'
        
                $connection = [System.Data.SqlClient.SqlConnection]::new($ConnectionString)
                $connection.Open()
        
                try {
                    $command = $connection.CreateCommand()
                    $command.CommandType = [System.Data.CommandType]::StoredProcedure
                    $command.CommandText = $StoredProcedure
        
                    $parameter = $command.Parameters.Add("@User_Name", [System.Data.SqlDbType]::NVarChar, 50)
                    $parameter.Value = if ($Username) { 
                        $Username 
                    }
                    else { 
                        [DBNull]::Value 
                    }
                    
        
                    $reader = $command.ExecuteReader()
                    $results = [System.Collections.Generic.List[pscustomobject]]::new()
        
                    while ($reader.Read()) {
                        $row = @{}
                        for ($i = 0; $i -lt $reader.FieldCount; $i++) {
                            $column = $reader.GetName($i)
                            $exclude = @('Password_Bytes', 'Salt_Bytes')
                            if ($column -notin $exclude) {
                                $row[$column] = $reader.GetValue($i)
                            }                
                        }
                        $results.Add([PSCustomObject]$row)
                    }
                    $reader.Close()
        
                    $results
                }
                catch {
                    Write-Error "An error occurred: $_"
                }
                finally {
                    $connection.Close()
                }
    }
}
#EndRegion '.\private\Invoke-GetUserStoredProc.ps1' 62
#Region '.\private\Invoke-NewDropPathStoredProc.ps1' -1

function Invoke-NewDropPathStoredProc {
    [CmdletBinding()]
    Param(
        [Parameter()]
        [String]
        $Feed,

        [Parameter()]
        [String]
        $DropPath = 'C:\drop'
    )


    # Create the Drop Path directory
    if (-not $PSBoundParameters.ContainsKey('DropPath')) {
        $DropPath = Join-Path $DropPath -ChildPath $Feed
    }

    if (-not (Test-Path $DropPath)) {
        $null = New-Item $DropPath -ItemType Directory
    }

    # Assign permissions to the Inedo service user to the Drop Path
    $ServiceUser = (Get-CimInstance Win32_Service -Filter "Name = 'INEDOPROGETWEBSVC'").StartName
    Set-ServiceUserPermission -FilePath $DropPath -ServiceUser $ServiceUser -Permissions Modify

    # Create SQL query with parameterized stored procedure execution
    $query = @"
DECLARE @Feed_Id INT
SET @Feed_Id = (SELECT Feed_Id FROM Feeds WHERE Feed_Name = @Feed)
 
EXEC [dbo].[Feeds_SetFeedProperty]
    @Feed_Id = @Feed_Id,
    @DropPath_Text = @DropPath
"@


    # Define SQL connection
    $connectionString = Get-ProGetConnectionString
    $connection = New-Object System.Data.SqlClient.SqlConnection
    $connection.ConnectionString = $connectionString
    $connection.Open()

    # Create and execute SQL command
    $command = $connection.CreateCommand()
    $command.CommandText = $query
    $null = $command.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@Feed", $Feed)))
    $null = $command.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@DropPath", $DropPath)))
    $null = $command.ExecuteNonQuery()

    # Close connection
    $connection.Close()
}
#EndRegion '.\private\Invoke-NewDropPathStoredProc.ps1' 53
#Region '.\private\Invoke-NewUserStoredProc.ps1' -1

function Invoke-NewUserStoredProc {
  <#
    .SYNOPSIS
    Private function that executes dbo.Users_CreateOrUpdateUser stored procedure in ProGet Database
  #>

  param (
    [Parameter()]
    [string]
    $ConnectionString = 'Server=Localhost\SQLEXPRESS;Database=ProGet;Trusted_Connection=true;',

    [Parameter()]
    [string]
    $StoredProcedure = 'dbo.Users_CreateOrUpdateUser',

    [Parameter(Mandatory)]
    [Hashtable]
    $Params,

    [Parameter()]
    [switch]
    $UseRemoting,

    [Parameter()]
    [string]
    $RemoteComputer,

    [Parameter()]
    [pscredential]
    $Credential
  )

  end {
    if ($UseRemoting) {
      # Execute on a remote machine using PowerShell remoting
      $scriptBlock = {
        param ($ConnectionString, $StoredProcedure, $Params)

        Add-Type -AssemblyName "System.Data"

        $connection = New-Object System.Data.SqlClient.SqlConnection
        $connection.ConnectionString = $ConnectionString
        $connection.Open()

        try {
          $command = $connection.CreateCommand()
          $command.CommandType = [System.Data.CommandType]::StoredProcedure
          $command.CommandText = $StoredProcedure

          foreach ($key in $Params.Keys) {
            $command.Parameters.AddWithValue($key, $Params[$key]) | Out-Null
          }

          $null = $command.ExecuteNonQuery()
        }
        catch {
          Write-Error "An error occurred: $_"
        }
        finally {
          $connection.Close()
        }
      }

      if ($Credential) {
        Invoke-Command -ComputerName $RemoteComputer -Credential $Credential -ScriptBlock $scriptBlock -ArgumentList $ConnectionString, $StoredProcedure, $Params
      }
      else {
        Invoke-Command -ComputerName $RemoteComputer -ScriptBlock $scriptBlock -ArgumentList $ConnectionString, $StoredProcedure, $Params
      }
    }

    else {
      # Execute locally
      Add-Type -AssemblyName "System.Data"

      $connection = New-Object System.Data.SqlClient.SqlConnection
      $connection.ConnectionString = $ConnectionString
      $connection.Open()

      try {
        $command = $connection.CreateCommand()
        $command.CommandType = [System.Data.CommandType]::StoredProcedure
        $command.CommandText = $StoredProcedure

        foreach ($key in $Params.Keys) {
          $command.Parameters.AddWithValue($key, $Params[$key]) | Out-Null
        }

        $null = $command.ExecuteNonQuery()
      }
      catch {
        Write-Error "An error occurred: $_"
      }
      finally {
        $connection.Close()
      }
    }
  }
}
#EndRegion '.\private\Invoke-NewUserStoredProc.ps1' 99
#Region '.\private\Invoke-ProGet.ps1' -1

function Invoke-ProGet {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory, Position = 0)]
        [String]
        $Slug,

        [Parameter()]
        [String]
        $Method = 'GET',

        [Parameter()]
        [Hashtable]
        $Body,

        [Parameter()]
        [Hashtable]
        $Form,

        [Parameter()]
        [String]
        $BodyJson,

        [Parameter()]
        [String]
        $File,

        [Parameter()]
        [String]
        $ContentType = 'application/json'
    )

    begin {
        $Configuration = Get-ProGetConfiguration
        
        # If we call this from New-ProGetFeed we have to use a special API key because ProGet is...well, silly.
        $caller = (Get-PSCallStack)[1].Command
    }

    end {

        $ssl = if ($Configuration['UseSSL']) {
            @{Protocol = 'https' ; Port = $Configuration['SslPort'] }
        }
        else {
            @{Protocol = 'http'; Port = $Configuration['NonSslPort'] }
        }
        $Uri = '{0}:{1}{2}' -f "$($ssl['Protocol'])://$($Configuration['Hostname'])", $ssl['Port'], $Slug.TrimEnd('/')

        Write-Verbose -Message $Uri
        $params = @{
            Uri                  = $Uri
            Method               = $Method
            ContentType          = $ContentType
            Headers              = if ($caller -ne 'New-ProGetFeed') {
                @{'X-ApiKey' = $Configuration.Credential.GetNetworkCredential().Password }
            } 
            else {
                @{'X-ApiKey' = $Configuration.ApiKey.GetNetworkCredential().Password }
            }
            SkipCertificateCheck = $true
            Verbose              = $false
        }

        if ($Body) {
            $params['Body'] = $Body | ConvertTo-Json -Depth 5
        }

        if ($BodyJson) {
            $params['Body'] = $BodyJson
        }

        if ($Form) {
            $params['Form'] = $Form
            $params.Remove('ContentType')
        }

        if ($File) {
            $fileContent = [System.IO.File]::ReadAllBytes($File)
            $params['Body'] = $fileContent
            $params['ContentType'] = 'application/octet-stream'
            Write-Verbose $params.Uri
        }

        #Write-Verbose ($Body | ConvertTo-Json -Depth 5)
        Invoke-RestMethod @params
    }
}
#EndRegion '.\private\Invoke-ProGet.ps1' 89
#Region '.\private\Invoke-UserPasswordStoredProc.ps1' -1

function Invoke-UserPasswordStoredProc {
    <#
        .SYNOPSIS
        Private function that executes dbo.Users_SetPassword stored procedure in ProGet Database
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory)]
        [Hashtable]        
        $Params,
        
        [Parameter(DontShow)]
        [String]
        $ConnectionString = 'Server=Localhost\SQLEXPRESS;Database=ProGet;Trusted_Connection=true;'
    )

    end {
        $StoredProcedure = 'dbo.Users_SetPassword'
        if ($UseRemoting) {
            # Execute on a remote machine using PowerShell remoting
            $scriptBlock = {
                param ($ConnectionString, $StoredProcedure, $Params)
      
                Add-Type -AssemblyName "System.Data"
      
                $connection = New-Object System.Data.SqlClient.SqlConnection
                $connection.ConnectionString = $ConnectionString
                $connection.Open()
      
                try {
                    $command = $connection.CreateCommand()
                    $command.CommandType = [System.Data.CommandType]::StoredProcedure
                    $command.CommandText = $StoredProcedure
                    $command.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@User_Name", [Data.SqlDbType]::NVarChar, 50))).Value = $Params['User_Name']
                    $command.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@Password_Bytes", [Data.SqlDbType]::Binary, 20))).Value = $Params['Password_Bytes']
                    $command.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@Salt_Bytes", [Data.SqlDbType]::Binary, 10))).Value = $Params['Salt_Bytes']
      
                    $null = $command.ExecuteNonQuery()
                }
                catch {
                    Write-Error "An error occurred: $_"
                }
                finally {
                    $connection.Close()
                }
            }
      
            if ($Credential) {
                Invoke-Command -ComputerName $RemoteComputer -Credential $Credential -ScriptBlock $scriptBlock -ArgumentList $ConnectionString, $StoredProcedure, $Params
            }
            else {
                Invoke-Command -ComputerName $RemoteComputer -ScriptBlock $scriptBlock -ArgumentList $ConnectionString, $StoredProcedure, $Params
            }
        }
      
        else {
            # Execute locally
            Add-Type -AssemblyName "System.Data"
      
            $connection = New-Object System.Data.SqlClient.SqlConnection
            $connection.ConnectionString = $ConnectionString
            $connection.Open()
      
            try {
                $command = $connection.CreateCommand()
                $command.CommandType = [System.Data.CommandType]::StoredProcedure
                $command.CommandText = $StoredProcedure
      
                $command.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@User_Name", [Data.SqlDbType]::NVarChar, 50))).Value =  $Params['User_Name']
                $command.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@Password_Bytes", [Data.SqlDbType]::Binary, 20))).Value = $Params['Password_Bytes']
                $command.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@Salt_Bytes", [Data.SqlDbType]::Binary, 10))).Value = $Params['Salt_Bytes']
  
                $null = $command.ExecuteNonQuery()
            }
            catch {
                Write-Error "An error occurred: $_"
            }
            finally {
                $connection.Close()
            }
        }
    }

}
#EndRegion '.\private\Invoke-UserPasswordStoredProc.ps1' 85
#Region '.\private\Set-CertPermissions.ps1' -1

function Set-CertPermissions {
    <#
    .SYNOPSIS
    Sets the permissions on the private key for a given certificate
     
    .DESCRIPTION
    Long description
     
    .PARAMETER Thumbprint
    Parameter description
     
    .PARAMETER Location
    Parameter description
     
    .PARAMETER Store
    Parameter description
     
    .PARAMETER ServiceUser
    Parameter description
     
    .EXAMPLE
    An example
     
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory)]
        [String]
        $Thumbprint,

        [Parameter(Mandatory)]
        [String]
        $Location,
  
        [Parameter(Mandatory)]
        [String]
        $Store,

        [Parameter(Mandatory)]
        [String]
        $ServiceUser

    )
    end {

        # Load the certificate from the specified store
        $certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store $Store, $Location
        $certStore.Open("ReadOnly")
        $certificate = $certStore.Certificates | Where-Object { $_.Thumbprint -eq $thumbprint }

        if (-not $certificate) {
            Write-Error "Certificate with thumbprint $thumbprint not found in LocalMachine\My."
            $certStore.Close()
            return
        }

        # Get the private key file path
        $privateKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($certificate)
        $uniqueName = $privateKey.key.uniquename
        $keyPath = Join-Path $env:ProgramData "Microsoft\Crypto\RSA\MachineKeys\$($uniquename)"
        
        # Close the certificate store if we have a private key, otherwise error that there is a problem finding a private key
        if (Test-Path $keyPath) {
            $certStore.Close()
        }
        else {
            Write-Error "Unable to locate the private key for the certificate."
            $certStore.Close()
            return
        }

        # Grant Inedo Service user read access to the private key
        $ServiceUser = (Get-CimInstance Win32_Service -Filter "Name = 'INEDOPROGETWEBSVC'").StartName
        Set-ServiceUserPermission -FilePath $keyPath -ServiceUser $ServiceUser -Permissions Read
    }
}
#EndRegion '.\private\Set-CertPermissions.ps1' 79
#Region '.\private\Set-ServiceUserPermission.ps1' -1

function Set-ServiceUserPermission {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory)]
        [String]
        $FilePath,

        [Parameter(Mandatory)]
        [String]
        $ServiceUser,

        [Parameter(Mandatory)]
        [ValidateSet('Read', 'ReadExecute', 'Modify', 'FullControl')]
        [String]
        $Permissions
    )

    begin {
        $caller = (Get-PSCallStack)[1].Command
    }

    end {
        $acl = Get-Acl $FilePath
        $inheritanceFlags = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
        $PermissionSet = @{
            Read        = [System.Security.AccessControl.FileSystemRights]::Read
            ReadExecute = [System.Security.AccessControl.FileSystemRights]::ReadAndExecute
            Modify      = [System.Security.AccessControl.FileSystemRights]::Modify
            FullControl = [System.Security.AccessControl.FileSystemRights]::FullControl

        }

        if ($caller -eq 'Set-CertPermissions') {
            # Grant ProGet service user read access to the private key
            $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
                $ServiceUser, # User
                $PermissionSet[$Permissions], # Permissions
                "Allow"
            )
        } 
        else {
            $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
                $ServiceUser, # User
                $PermissionSet[$Permissions], # Permissions
                $inheritanceFlags, # Inheritance
                [System.Security.AccessControl.PropagationFlags]::None, #Apply normal inheritance
                "Allow"
            )
        }

        $acl.SetAccessRule($accessRule)
        Set-Acl -Path $FilePath -AclObject $acl
    }
}
#EndRegion '.\private\Set-ServiceUserPermission.ps1' 55
#Region '.\public\Assets\New-ProGetAsset.ps1' -1

function New-ProGetAsset {
    <#
        .Synopsis
        Transfers a file to a ProGet asset directory.
        
        .Description
        Transfers a file to a ProGet asset directory. This function performs automatic chunking
        if the file is larger than a specified threshold.
        
        .Parameter FileName
        Name of the file to upload from the local file system.
         
        .Parameter EndpointUrl
        Full URL of the ProGet asset directory's API endpoint. This is typically something like http://proget/endpoints/<directoryname>
         
        .Parameter AssetName
        Full path of the asset to create in ProGet's asset directory.
         
        .Parameter ChunkSize
        Uploads larger than this value will be uploaded using multiple requests. The default is 5 MB.
        
        .Example
        New-ProGetAsset -FileName C:\Files\Image.jpg -AssetName images/image.jpg -EndpointUrl http://proget/endpoints/MyAssetDir
    #>

    [CmdletBinding(HelpUri = 'https://steviecoaster.github.io/Pagootle/Commands/Commands/New-ProGetAsset')]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $fileName,

        [Parameter()]
        [String]
        $AssetDirectory,

        [Parameter()]
        [string]
        $Slug = '/endpoints/{0}' -f $AssetDirectory,

        [Parameter(Mandatory = $true)]
        [string]
        $assetName,

        [Parameter()]
        [int]
        $chunkSize = 5 * 1024 * 1024
    )

    begin {
        $Configuration = Get-ProGetConfiguration

        $ssl = if ($Configuration['UseSSL']) {
            'https'
        }
        else {
            'http'
        }

    }

    process {

        $endpointUrl = "$($ssl)://$($Configuration['Hostname']):8443"
        if (-not $endpointUrl.EndsWith('/')) { $endpointUrl += '/' }
        $targetUrl = $endpointUrl + 'content/' + "$AssetDirectory/" + [Uri]::EscapeUriString($assetName.Replace('\', '/'))
        Write-Verbose $targetUrl
        $fileInfo = Get-ChildItem -Path $fileName

        if ($fileInfo.Length -le $chunkSize) {
            Invoke-WebRequest -Method Post -Uri $targetUrl -InFile $fileName
        }
        else {
            $sourceStream = [System.IO.FileStream]::new($fileName, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read, 4096, [System.IO.FileOptions]::SequentialScan)

            try {
                $fileLength = $sourceStream.Length
                $remainder = 0
                $totalParts = [Math]::DivRem($fileLength, $chunkSize, [ref]$remainder)
                if ($remainder -ne 0) { $totalParts++ }
                $uuid = [Guid]::NewGuid().ToString("N")

                0..($totalParts - 1) | ForEach-Object {
                    $index = $_
                    $offset = $index * $chunkSize
                    $currentChunkSize = if ($index -eq ($totalParts - 1)) { $fileLength - $offset } else { $chunkSize }

                    $req = [System.Net.WebRequest]::CreateHttp("${targetUrl}?multipart=upload&id=$uuid&index=$index&offset=$offset&totalSize=$fileLength&partSize=$currentChunkSize&totalParts=$totalParts")
                    $req.Method = 'POST'
                    $req.ContentLength = $currentChunkSize
                    $req.AllowWriteStreamBuffering = $false
                    $reqStream = $req.GetRequestStream()

                    try { CopyMaxBytes -source $sourceStream -target $reqStream -maxBytes $currentChunkSize -startOffset $offset -totalSize $fileLength }
                    finally { if ($reqStream) { $reqStream.Dispose() } }

                    $response = $req.GetResponse()
                    try { } finally { if ($response) { $response.Dispose() } }
                }

                Write-Progress -Activity "Uploading $fileName..." -Status "Completing upload..." -PercentComplete -1

                $req = [System.Net.WebRequest]::CreateHttp("${targetUrl}?multipart=complete&id=$uuid")
                $req.Method = 'POST'
                $req.ContentLength = 0
                $response = $req.GetResponse()
                try { } finally { if ($response) { $response.Dispose() } }
            }
            finally { if ($sourceStream) { $sourceStream.Dispose() } }
        }
    }
}
#EndRegion '.\public\Assets\New-ProGetAsset.ps1' 111
#Region '.\public\Assets\Publish-ProGetAsset.ps1' -1

function Publish-ProGetAsset {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory)]
        [String]
        $Feed,

        [Parameter(Mandatory)]
        [String[]]
        $File
    )
    end {

        foreach ($F in $File) {
            $params = @{
                Slug   = "/endpoints/$Feed/content/{0}" -f (Split-Path $F -Leaf)
                Method = 'PUT'
                File   = $F
            }

            Invoke-ProGet @params
        }
    }
}
#EndRegion '.\public\Assets\Publish-ProGetAsset.ps1' 25
#Region '.\public\Feeds\Connectors\Get-ProGetConnector.ps1' -1

function Get-ProGetConnector {
<#
    .SYNOPSIS
    Retrieves information about connectors in ProGet.
 
    .DESCRIPTION
    The `Get-ProGetConnector` function retrieves details about one or more connectors in ProGet. If a connector name is provided, it retrieves information for that specific connector. If no name is provided, it lists all connectors.
 
    .PARAMETER Name
    The name of the connector to retrieve. If not specified, all connectors are listed.
 
    .EXAMPLE
    Get-ProGetConnector
 
    Retrieves a list of all connectors in ProGet.
 
    .EXAMPLE
    Get-ProGetConnector -Name "MyConnector"
 
    Retrieves details about the connector named "MyConnector".
#>

    [CmdletBinding(HelpUri = 'https://steviecoaster.github.io/Pagootle/Commands/Commands/Get-ProGetConnector')]
    Param(
        [Parameter()]
        [String]
        $Name
    )

    process{
        $params = @{
            Method = 'GET'
        }

        if($Name){
            $params.add('Slug',"/api/management/connectors/get/$Name")
        }
        else {
            $params.Add('Slug','/api/management/connectors/list')
        }

        Invoke-ProGet @params
    }
}
#EndRegion '.\public\Feeds\Connectors\Get-ProGetConnector.ps1' 44
#Region '.\public\Feeds\Connectors\New-ProGetConnector.ps1' -1

function New-ProGetConnector {
<#
    .SYNOPSIS
    Creates a new connector in ProGet.
 
    .DESCRIPTION
    The `New-ProGetConnector` function allows you to create a new connector in ProGet. Connectors are used to link feeds to external sources, such as NuGet, Chocolatey, Docker, and others. This function supports various feed types and allows configuration of metadata caching, filters, and credentials.
 
    .PARAMETER Name
    The name of the connector. This parameter is mandatory.
 
    .PARAMETER FeedType
    The type of feed the connector is associated with. Supported types include 'universal', 'nuget', 'chocolatey', 'npm', 'maven', 'powershell', 'docker', 'rubygems', 'vsix', 'asset', 'romp', 'pypi', 'helm', 'rpm', 'conda', and 'cran'. This parameter is mandatory.
 
    .PARAMETER Url
    The URL of the external source for the connector. If not specified, a default URL is used based on the feed type.
 
    .PARAMETER Timeout
    The timeout value (in seconds) for the connector. This parameter is mandatory.
 
    .PARAMETER Credential
    A PSCredential object containing the username and password for authentication with the external source.
 
    .PARAMETER Filters
    An array of filters to apply to the connector.
 
    .PARAMETER MetadataCacheEnabled
    Enables metadata caching for the connector.
 
    .PARAMETER MetadataCacheMinutes
    Specifies the duration (in minutes) for which metadata is cached. Defaults to 30 minutes.
 
    .PARAMETER MetadataCacheCount
    Specifies the maximum number of metadata items to cache. Defaults to 100 items.
 
    .EXAMPLE
    New-ProGetConnector -Name "NuGetConnector" -FeedType "nuget" -Timeout 60
 
    Creates a new NuGet connector named "NuGetConnector" with a timeout of 60 seconds.
 
    .EXAMPLE
    New-ProGetConnector -Name "DockerConnector" -FeedType "docker" -Url "https://custom.docker.registry" -Timeout 120 -Credential (Get-Credential)
 
    Creates a new Docker connector named "DockerConnector" with a custom URL, a timeout of 120 seconds, and authentication credentials.
 
    .EXAMPLE
    New-ProGetConnector -Name "FilteredConnector" -FeedType "npm" -Filters @("filter1", "filter2") -MetadataCacheEnabled
 
    Creates a new NPM connector named "FilteredConnector" with specified filters and metadata caching enabled.
#>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory)]
        [String]
        $Name,

        [Parameter(Mandatory)]
        [ValidateSet('universal','nuget','chocolatey','npm','maven','powershell','docker','rubygems','vsix','asset','romp','pypi','helm','rpm','conda','cran')]
        [String]
        $FeedType,

        [Parameter()]
        [String]
        $Url,

        [Parameter(Mandatory)]
        [Int]
        $Timeout,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        $Credential,

        [Parameter()]
        [String[]]
        $Filters,

        [Parameter()]
        [Switch]
        $MetadataCacheEnabled,

        [Parameter()]
        [Int]
        $MetadataCacheMinutes = 30, #Hard default in ProGet, default value is ignored by this module code due to PSBoundParameters

        [Parameter()]
        [Int]
        $MetadataCacheCount = 100 #Hard default in ProGet, default value is ignored by this module code due to PSBoundParameters
    )

    process {

        $feedUrlDefaults = @{
            universal = $null
            nuget = 'https://api.nuget.org/v3/index.json'
            chocolatey = 'https://community.chocolatey.org/api/v2'
            npm = 'https://registry.npmjs.org'
            maven = 'https://repo1.maven.org/maven2'
            bower = 'https://registry.bower.io/packages' 
            docker = 'https://registry.hub.docker.com'
            debian = 'http://ftp.debian.org/debian/' 
            pypi = 'https://pypi.org'
            conda = 'https://repo.anaconda.com/pkgs/main/'
            cran = 'https://cran.r-project.org/'
        }

        $body = @{}

        if(-not $url){
            $body.Add('url',$feedUrlDefaults["$FeedType"]) #if they pass one, the url will get updated in the enumeration below, so this is fine.
        }

        $PSBoundParameters.GetEnumerator() | ForEach-Object {
            if($_.Key -eq 'Credential'){
                $tempCred = $_.Value
                $body.Add('username',$tempCred.Username)
                $body.Add('password',$tempCred.GetNetworkCredential().Password)
            } else {
                $body.Add($_.Key,$_.Value)
            }
        }


        $params = @{
            Slug = '/api/management/connectors/create'
            Method = 'POST'
            Body = $body
        }

        Invoke-Proget @params
    }
}
#EndRegion '.\public\Feeds\Connectors\New-ProGetConnector.ps1' 133
#Region '.\public\Feeds\Connectors\Remove-ProGetConnector.ps1' -1

function Remove-ProGetConnector {
<#
    .SYNOPSIS
    Removes a connector from ProGet.
 
    .DESCRIPTION
    The `Remove-ProGetConnector` function deletes a specified connector from ProGet. It supports confirmation prompts to prevent accidental deletions and can be forced to skip confirmation. The function uses the ProGet API to perform the deletion.
 
    .PARAMETER Connector
    The name of the connector(s) to remove. This parameter is mandatory and supports pipeline input.
 
    .PARAMETER Force
    Skips the confirmation prompt and forces the removal of the connector(s).
 
    .EXAMPLE
    Remove-ProGetConnector -Connector "MyConnector"
 
    Removes the connector named "MyConnector" after confirmation.
 
    .EXAMPLE
    Remove-ProGetConnector -Connector "MyConnector" -Force
 
    Forces the removal of the connector named "MyConnector" without confirmation.
 
    .EXAMPLE
    Get-ProGetConnector | Remove-ProGetConnector
 
    Pipes the output of `Get-ProGetConnector` to remove all listed connectors after confirmation.
#>

    [CmdletBinding(ConfirmImpact = 'High', SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory)]
        [Alias('Name')]
        [ArgumentCompleter({
            param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
        
            # Call Get-ProGetFeed and parse its output to get feed names
            $connectors = (Get-ProGetConnector).name
        
            if ($wordToComplete) {
                $connectors.Where{ $_ -match "^$WordToComplete" }
            }
            else {
                $connectors
            }
         
        })]
        [String[]]
        $Connector,

        [Parameter()]
        [Switch]
        $Force
    )

    $Connector | ForEach-Object {
        if ($Force -and -not $Confirm) {
            $ConfirmPreference = 'None'
            if ($PSCmdlet.ShouldProcess("$_", 'Remove Connector')) {
                $params = @{
                    Slug   = "/api/management/connectors/delete/$_"
                    Method = 'DELETE'
                }

                Invoke-ProGet @params
            }
        }
        else {
            if ($PSCmdlet.ShouldProcess("$_", 'Remove Connector')) {
                $params = @{
                    Slug   = "/api/management/connectors/delete/$_"
                    Method = 'DELETE'
                }
            }

            Invoke-ProGet @params
        }

    }
}
#EndRegion '.\public\Feeds\Connectors\Remove-ProGetConnector.ps1' 81
#Region '.\public\Feeds\Get-ProGetFeed.ps1' -1

function Get-ProGetFeed {
    <#
    .SYNOPSIS
    Returns information about the available feeds in your ProGet instance
     
    .DESCRIPTION
    Supports returning all feeds, feeds by type, or by name
     
    .PARAMETER Feed
    The feed to return
     
    .PARAMETER Type
    The type of feed to return
     
    .EXAMPLE
    Get-ProGetFeed
 
    Return all feeds
 
    .EXAMPLE
    Get-ProGetFeed -Type nuget
 
    Return all nuget feeds
 
    .EXAMPLE
 
    Get-ProGetFeed -Feed ChocolateyPackages
 
    Return the ChocolateyPackages feed
     
    .NOTES
 
    #>

    [CmdletBinding(HelpUri = 'https://steviecoaster.github.io/Pagootle/Commands/Get-ProGetFeed')]
    Param(
        [Parameter()]
        [String]
        $Feed,

        [Parameter()]
        [ValidateSet('asset', 'bower', 'conda', 'chocolatey', 'debianlegacy', 'debian', 'docker', 'helm', 'maven', 'npm', 'nuget', 'powershell', 'universal', 'pypi', 'romp', 'rpm', 'rubygems', 'vsix')]
        [String]
        $Type
    )
    end {
        if($Feed -and $Type){
            throw 'Only one parameter of Feed or Type can be used at a time'
        }
        $params = @{
            Method = 'GET'
        }

        if ($Feed) {
            $params['Slug'] = '/api/management/feeds/get/{0}' -f $Feed
        }
        else {
            $params['Slug'] = '/api/management/feeds/list'
        }

        $result = Invoke-ProGet @params

        if ($Type) {
            $filter = { $_.feedType -eq $Type }
            $result | Where-Object $filter
        }
        else {
            $result
        }
    }
}
#EndRegion '.\public\Feeds\Get-ProGetFeed.ps1' 71
#Region '.\public\Feeds\New-ProGetFeed.ps1' -1

function New-ProGetFeed {
    <#
    .SYNOPSIS
    Creates a new feed in ProGet.
 
    .DESCRIPTION
    The `New-ProGetFeed` function allows you to create a new feed in ProGet with various configurable options, such as feed type, connectors, retention rules, and more. It supports multiple feed types, including NuGet, Chocolatey, Docker, and others.
 
    .PARAMETER Name
    The name of the feed to create. This parameter is mandatory.
 
    .PARAMETER AlternateNames
    Alternate names for the feed.
 
    .PARAMETER Type
    The type of the feed. Supported types include 'asset', 'bower', 'conda', 'chocolatey', 'debianlegacy', 'debian', 'docker', 'helm', 'maven', 'npm', 'nuget', 'powershell', 'universal', 'pypi', 'romp', 'rpm', 'rubygems', and 'vsix'. Defaults to 'NuGet'.
 
    .PARAMETER Active
    Specifies whether the feed is active.
 
    .PARAMETER CacheConnectors
    Specifies whether connectors should be cached.
 
    .PARAMETER SymbolServerEnabled
    Enables the symbol server for the feed.
 
    .PARAMETER StripSymbols
    Specifies whether symbols should be stripped from packages.
 
    .PARAMETER StripSource
    Specifies whether source files should be stripped from packages.
 
    .PARAMETER EndpointUrl
    The endpoint URL for the feed.
 
    .PARAMETER Connectors
    A list of connectors to associate with the feed.
 
    .PARAMETER RetentionRules
    A list of retention rules to apply to the feed.
 
    .PARAMETER Variables
    A hashtable of variables to associate with the feed.
 
    .PARAMETER CanPublish
    Specifies whether publishing to the feed is allowed.
 
    .PARAMETER PackageStatisticsEnabled
    Enables package statistics for the feed.
 
    .PARAMETER RestrictPackageStatistics
    Restricts access to package statistics.
 
    .PARAMETER DeploymentRecordsEnabled
    Enables deployment records for the feed.
 
    .PARAMETER UsageRecordsEnabled
    Enables usage records for the feed.
 
    .PARAMETER VulnerabilitiesEnabled
    Enables vulnerability tracking for the feed.
 
    .PARAMETER LicensesEnabled
    Enables license tracking for the feed.
 
    .PARAMETER UseWithProjects
    Specifies whether the feed can be used with projects.
 
    .EXAMPLE
    New-ProGetFeed -Name "MyFeed" -Type "nuget" -Active -CanPublish
 
    Creates a new NuGet feed named "MyFeed" that is active and allows publishing.
 
    .EXAMPLE
    New-ProGetFeed -Name "DockerFeed" -Type "docker" -Connectors @("Connector1", "Connector2") -RetentionRules @("Rule1", "Rule2")
 
    Creates a new Docker feed named "DockerFeed" with specified connectors and retention rules.
 
    #>

    [CmdletBinding(HelpUri = 'https://steviecoaster.github.io/Pagootle/Commands/New-ProGetFeed')]
    Param(
        [Parameter(Mandatory)]
        [String]
        $Name,

        [Parameter()]
        [String]
        $AlternateNames,

        [Parameter()]
        [ValidateSet('asset', 'bower', 'conda', 'chocolatey', 'debianlegacy', 'debian', 'docker', 'helm', 'maven', 'npm', 'nuget', 'powershell', 'universal', 'pypi', 'romp', 'rpm', 'rubygems', 'vsix')]        [String]
        $Type = 'NuGet',

        [Parameter()]
        [Switch]
        $Active,

        [Parameter()]
        [Switch]
        $CacheConnectors,

        [Parameter()]
        [Switch]
        $SymbolServerEnabled,

        [Parameter()]
        [Switch]
        $StripSymbols,

        [Parameter()]
        [Switch]
        $StripSource,

        [Parameter()]
        [String]
        $EndpointUrl,

        [Parameter()]
        [String[]]
        $Connectors,

        [Parameter()]
        [String[]]
        $RetentionRules,

        [Parameter()]
        [Hashtable]
        $Variables,

        [Parameter()]
        [Switch]
        $CanPublish,

        [Parameter()]
        [Switch]
        $PackageStatisticsEnabled,

        [Parameter()]
        [Switch]
        $RestrictPackageStatistics,

        [Parameter()]
        [Switch]
        $DeploymentRecordsEnabled,

        [Parameter()]
        [Switch]
        $UsageRecordsEnabled,

        [Parameter()]
        [Switch]
        $VulnerabilitiesEnabled,

        [Parameter()]
        [Switch]
        $LicensesEnabled,

        [Parameter()]
        [Switch]
        $UseWithProjects
    )

    end {
        $params = @{
            Slug   = '/api/management/feeds/create'
            Method = 'POST'
            Body   = @{
                name                      = $Name
                alternateNames            = $AlternateNames
                feedType                  = $Type
                active                    = $Active.IsPresent
                cacheConnectors           = $CacheConnectors.IsPresent
                symbolServerEnabled       = $SymbolServerEnabled.IsPresent
                stripSymbols              = $StripSymbols.IsPresent
                stripsource               = $StripSource.IsPresent
                endpointUrl               = $EndpointUrl
                connectors                = $Connectors
                retentionRules            = $RetentionRules
                variables                 = $Variables
                canPublish                = $CanPublish.IsPresent
                packageStatisticsEnabled  = $PackageStatisticsEnabled.IsPresent
                restrictPackageStatistics = $RestrictPackageStatistics.IsPresent
                deploymentRecordsEnabled  = $DeploymentRecordsEnabled.IsPresent
                usageRecordsEnabled       = $UsageRecordsEnabled.IsPresent
                vulnerabilitiesEnabled    = $VulnerabilitiesEnabled.IsPresent
                licenseEnabled            = $LicensesEnabled.IsPresent
                useWithProjects           = $UseWithProjects.IsPresent
            }
        }

        if ($Type -eq 'chocolatey') {
            $params.Body.Add('useApiV3', $True)
        }

        Invoke-ProGet @params
    }
}
#EndRegion '.\public\Feeds\New-ProGetFeed.ps1' 198
#Region '.\public\Feeds\New-ProGetFeedDropPath.ps1' -1

function New-ProGetFeedDropPath {
    <#
    .SYNOPSIS
    Creates or updates the drop path for a ProGet feed.
 
    .DESCRIPTION
    The `New-ProGetFeedDropPath` function allows you to set or update the drop path for a specified ProGet feed.
    The drop path is used to specify a directory where packages can be dropped for processing by the feed.
 
    .PARAMETER Feed
    The name of the feed for which the drop path is being set. This parameter is mandatory.
 
    .PARAMETER DropPath
    The directory path to set as the drop path for the feed. If not specified, the drop path will be cleared.
 
    .EXAMPLE
    New-ProGetFeedDropPath -Feed "MyFeed" -DropPath "C:\Packages\Drop"
 
    Sets the drop path for the feed "MyFeed" to "C:\Packages\Drop".
 
    .EXAMPLE
    New-ProGetFeedDropPath -Feed "MyFeed"
 
    Drop Path will default to C:\Drop\MyFeed.
 
    #>

    [CmdletBinding(HelpUri = 'https://steviecoaster.github.io/Pagootle/Commands/New-ProGetFeedDropPath')]
    Param(
        [Parameter(Mandatory)]
        [String]
        $Feed,

        [Parameter()]
        [String]
        $DropPath
    )
    
    $dropArgs = @{
        Feed = $Feed
    }

    if ($DropPath) {
        $dropArgs.Add('DropPath', $DropPath)
    }

    Invoke-NewDropPathStoredProc @dropArgs    
}
#EndRegion '.\public\Feeds\New-ProGetFeedDropPath.ps1' 48
#Region '.\public\Feeds\Remove-ProGetFeed.ps1' -1

function Remove-ProGetFeed {
<#
    .SYNOPSIS
    Removes a feed from ProGet.
 
    .DESCRIPTION
    The `Remove-ProGetFeed` function deletes a specified feed from ProGet. It supports pipeline input and provides confirmation prompts to prevent accidental deletions. The function uses the ProGet API to perform the deletion.
 
    .PARAMETER Feed
    The name of the feed(s) to remove. This parameter is mandatory and supports pipeline input.
 
    .PARAMETER Force
    Skips the confirmation prompt and forces the removal of the feed(s).
 
    .EXAMPLE
    Remove-ProGetFeed -Feed "MyFeed"
 
    Removes the feed named "MyFeed" after confirmation.
 
    .EXAMPLE
    Remove-ProGetFeed -Feed "MyFeed" -Force
 
    Forces the removal of the feed named "MyFeed" without confirmation.
 
    .EXAMPLE
    Get-ProGetFeed | Remove-ProGetFeed
 
    Pipes the output of `Get-ProGetFeed` to remove all listed feeds after confirmation.
#>

    [CmdletBinding(HelpUri = 'https://steviecoaster.github.io/Pagootle/Commands/Remove-ProGetFeed', ConfirmImpact = 'High', SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [Alias('Name')]
        [ArgumentCompleter({
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
            
                # Call Get-ProGetFeed and parse its output to get feed names
                $feeds = (Get-ProGetFeed).name
            
                if ($wordToComplete) {
                    $feeds.Where{ $_ -match "^$WordToComplete" }
                }
                else {
                    $feeds
                }
             
            })]
        [String[]]
        $Feed,

        [Parameter()]
        [Switch]
        $Force
    )
    process {
        $Feed | Foreach-Object {

            if($Force -and -not $Confirm){
                $ConfirmPreference = 'None'
                if($PSCmdlet.ShouldProcess("$_",'Remove Feed')){
                    $params = @{
                        Slug = "/api/management/feeds/delete/$_"
                        Method = 'DELETE'
                    }

                    Invoke-ProGet @params
                }
            }
            else {
                if($PSCmdlet.ShouldProcess("$_", 'Remove Feed')){
                    $params = @{
                        Slug = "/api/management/feeds/delete/$_"
                        Method = 'DELETE'
                    }

                    Invoke-ProGet @params
                }
            }
        }
    }
}
#EndRegion '.\public\Feeds\Remove-ProGetFeed.ps1' 82
#Region '.\public\ModuleConfiguration\Get-ProGetConfiguration.ps1' -1

function Get-ProGetConfiguration {
<#
    .SYNOPSIS
    Retrieves the configuration for connecting to ProGet.
 
    .DESCRIPTION
    The `Get-ProGetConfiguration` function retrieves the configuration for ProGet. By default, it retrieves the configuration for ProGet unless a different configuration name is provided.
 
    .PARAMETER Configuration
    The name of the configuration to retrieve. Defaults to 'ProGet'.
 
    .EXAMPLE
    Get-ProGetConfiguration
 
    Retrieves the configuration for ProGet.
 
    .EXAMPLE
    Get-ProGetConfiguration -Configuration "CustomConfig"
 
    Retrieves the configuration for the configuration named "CustomConfige".
#>

    [CmdletBinding(HelpUri = 'https://steviecoaster.github.io/Pagootle/Commands/Get-ProGetConfiguration')]
    Param(
        [Parameter()]
        [Alias('Name')]
        [String]
        $Configuration = 'ProGet'
    )
    end {
        Import-Configuration -CompanyName $env:USERNAME -Name $Configuration
    }

}
#EndRegion '.\public\ModuleConfiguration\Get-ProGetConfiguration.ps1' 34
#Region '.\public\ModuleConfiguration\Set-ProGetConfiguration.ps1' -1

function Set-ProGetConfiguration {
    <#
    .SYNOPSIS
    Sets the configuration for connecting to ProGet.
 
    .DESCRIPTION
    The `Set-ProGetConfiguration` function allows you to configure the connection settings for ProGet, including hostname, credentials, ports, and SSL options. The configuration can be saved with a custom name for later use.
 
    .PARAMETER Hostname
    The hostname of the ProGet server. This parameter is mandatory.
 
    .PARAMETER Credential
    A PSCredential object containing the username and password for authenticating with the ProGet server. This parameter is mandatory.
 
    .PARAMETER NonSslPort
    The port to use for non-SSL connections. Defaults to 8624.
 
    .PARAMETER UseSSL
    Specifies whether to use SSL for the connection. This parameter is part of the 'ssl' parameter set.
 
    .PARAMETER SslPort
    The port to use for SSL connections. Defaults to 443. This parameter is mandatory when `UseSSL` is specified.
 
    .PARAMETER Name
    The name of the configuration to save. Defaults to 'ProGet'.
 
    .PARAMETER ApiKey
    The API key to use for authentication. Defaults to 'SetMe'.
 
    .EXAMPLE
    Set-ProGetConfiguration -Hostname "proget.example.com" -Credential (Get-Credential)
 
    Sets the configuration for ProGet with the specified hostname and credentials.
 
    .EXAMPLE
    Set-ProGetConfiguration -Hostname "proget.example.com" -ApiKey asdf8675309
 
    Sets the configuration for ProGet with the specified hostname and apikey
 
    .EXAMPLE
    Set-ProGetConfiguration -Hostname "proget.example.com" -ApiKey asdf8675309 -Credential (Get-Credential)
 
    Sets the configuration for ProGet with the specified hostname, credential, and apikey
 
    .EXAMPLE
    Set-ProGetConfiguration -Hostname "proget.example.com" -Credential (Get-Credential) -UseSSL -SslPort 8443
 
    Sets the configuration for ProGet with SSL enabled and a custom SSL port.
 
    .EXAMPLE
    Set-ProGetConfiguration -Hostname "proget.example.com" -Credential (Get-Credential) -Name "CustomConfig"
 
    Sets the configuration for ProGet with a custom configuration name.
#>

    [CmdletBinding(HelpUri = 'https://steviecoaster.github.io/Pagootle/Commands/Set-ProGetConfiguration' , DefaultParameterSetName = 'Apikey')]
    Param(
        [Parameter(Mandatory)]
        [String]
        $Hostname,

        [Parameter(Mandatory, ParameterSetName = 'Credential')]
        [Parameter(Mandatory, ParameterSetName = 'Both')]
        [System.Management.Automation.PSCredential]
        $Credential,
        
        [Parameter()]
        [Int]
        $NonSslPort = '8624',

        [Parameter()]
        [Switch]
        $UseSSL,

        [Parameter()]
        [Int]
        $SslPort = 443,

        [Parameter()]
        [String]
        $Name = 'ProGet',

        [Parameter(Mandatory, ParameterSetName = 'Apikey')]
        [Parameter(Mandatory, ParameterSetName = 'Both')]
        [String]
        $ApiKey
    )

    end {
        $Configuration = @{
            Hostname   = $Hostname
            NonSslPort = $NonSslPort
        }

        if ($UseSSL) {
            $Configuration.Add('UseSSL', $UseSSL)
            $Configuration.Add('SSLPort', $SslPort)
        }

        switch ($PSCmdlet.ParameterSetName) {
            
            'Credential' {
                $Configuration.Add('Credential', $Credential)
            }

            'ApiKey' {
                $Configuration.Add('ApiKey', $([PSCredential]::new('null', ($ApiKey | ConvertTo-SecureString -AsPlainText -Force))))
            }

            'Both' {
                if (-not $Credential -or -not $ApiKey) {
                    throw "Both Credential and ApiKey must be provided when using the 'Both' parameter set."
                }
                
                $Configuration.Add('Credential', $Credential)
                $Configuration.Add('ApiKey', $([PSCredential]::new('null', ($ApiKey | ConvertTo-SecureString -AsPlainText -Force))))
            }
        }
        
        $Configuration | Export-Configuration -CompanyName $env:USERNAME -Name $Name -Scope User
    }
}
#EndRegion '.\public\ModuleConfiguration\Set-ProGetConfiguration.ps1' 122
#Region '.\public\Security\Groups\New-ProGetGroup.ps1' -1

function New-ProGetGroup {
    <#
    .SYNOPSIS
    Create a new user group in ProGet
         
    .PARAMETER Name
    The group to create
     
    .EXAMPLE
    New-ProGetGroup -Name SomeGroup
    #>

    [CmdletBinding(HelpUri = 'https://steviecoaster.github.io/Pagootle/Commands/New-ProGetGroup')]
    Param(
        [Parameter(Mandatory)]
        [String]
        $Name
    )
    end {
        Invoke-CreateGroupStoredProc -Name $Name
    }
}
#EndRegion '.\public\Security\Groups\New-ProGetGroup.ps1' 22
#Region '.\public\Security\Tasks\Get-ProGetTask.ps1' -1

function Get-ProGetTask {
<#
    .SYNOPSIS
    Retrieves tasks from ProGet.
 
    .DESCRIPTION
    The `Get-ProGetTask` function retrieves a list of tasks from ProGet by invoking the corresponding stored procedure.
 
    .EXAMPLE
    Get-ProGetTask
 
    Retrieves all tasks from ProGet.
#>

    [CmdletBinding(HelpUri = 'https://steviecoaster.github.io/Pagootle/Commands/Get-ProGetTask')]
    Param()
    
    end {
        Invoke-GetTaskStoredProc
    }
}
#EndRegion '.\public\Security\Tasks\Get-ProGetTask.ps1' 21
#Region '.\public\Security\Users\Get-ProGetUser.ps1' -1

function Get-ProGetUser {
<#
.SYNOPSIS
Returns user account data from ProGet
 
.PARAMETER Username
The username to return
 
.EXAMPLE
Get-ProGetUser
 
Return data for all defined users
 
.EXAMPLE
Get-ProGetUser -Username bob
 
Return data for user bob
#>

    [CmdletBinding(HelpUri = 'https://steviecoaster.github.io/Pagootle/Commands/Get-ProGetUser')]
    Param(
        [Parameter()]
        [String]
        $Username
    )

    end {
        Invoke-GetUserStoredProc @PSBoundParameters
    }
}
#EndRegion '.\public\Security\Users\Get-ProGetUser.ps1' 30
#Region '.\public\Security\Users\New-ProGetUser.ps1' -1

function New-ProGetUser {
    <#
    .SYNOPSIS
    Create a new ProGet user
     
    .DESCRIPTION
    Create a new ProGet user with the provided properties
     
    .PARAMETER Credential
    The credential object will set the username and password for the user
     
    .PARAMETER DisplayName
    The friendly name of the user account
     
    .PARAMETER EmailAddress
    The email address
     
    .PARAMETER Group
    Any groups the user should be included in
     
    .EXAMPLE
    New-ProGetUser -Credential (Get-Credential) -DisplayName 'Bobby Tables' -Group Users,Chocolatey
     
    .EXAMPLE
    New-ProGetUser -Credential $cred -DisplayName 'Jim Thome' -EmailAddress jim@fabrikam.com -Group Administrators
    #>

    [CmdletBinding(HelpUri = 'https://steviecoaster.github.io/Pagootle/Commands/New-ProGetUser')]
    Param(
        [Parameter(Mandatory)]
        [PSCredential]
        $Credential,

        [Parameter(Mandatory)]
        [String]
        $DisplayName,

        [Parameter()]
        [string]
        $EmailAddress,

        [Parameter(Mandatory)]
        [String[]]
        $Group
    )

    process {

        $groupXml = ConvertTo-XmlString -Group $Group

        $params = @{
            User_Name = $Credential.UserName
            Display_Name = $DisplayName
            Groups_Xml = $groupXml
        }

        if($EmailAddress){
            $params.add('Email_Address',$EmailAddress)
        }

        Invoke-NewUserStoredProc -Params $params
        Set-ProGetUserPassword -Credential $Credential

        
    }
}
#EndRegion '.\public\Security\Users\New-ProGetUser.ps1' 66
#Region '.\public\Security\Users\Set-ProGetUserPassword.ps1' -1

function Set-ProGetUserPassword {
<#
    .SYNOPSIS
    Sets the password for a ProGet user account
 
    .PARAMETER Credential
    The credential object that contains the username and updated password for the account you wish to update
     
    .EXAMPLE
    Set-ProGetUserPassword -Credential (Get-Credential)
 
    Pass a PSCredential object with the username and new password and the user account will be updated.
#>

    [Cmdletbinding(HelpUri = 'https://steviecoaster.github.io/Pagootle/Commands/Set-ProGetUserPassword')]
    Param(
        [Parameter(Mandatory)]
        [Alias('Username')]
        [PSCredential]
        $Credential
    )



    end {

        $iterations = 10000
        $saltLength = 10
        $hashLength = 20

        $rfc2898 = [System.Security.Cryptography.Rfc2898DeriveBytes]::new($Credential.GetNetworkCredential().Password, $saltLength, $iterations)
        $passwordBytes = $rfc2898.GetBytes($hashLength)
        $saltBytes = $rfc2898.Salt

        $Params = @{
            User_Name      = $Credential.UserName
            Password_Bytes = $passwordBytes
            Salt_Bytes     = $saltBytes
        }

        Invoke-UserPasswordStoredProc -Params $Params
    }
}
#EndRegion '.\public\Security\Users\Set-ProGetUserPassword.ps1' 43
#Region '.\public\Utility\Merge-Directory.ps1' -1

function Merge-Directory {
    <#
    .SYNOPSIS
    Merges the contents of a directory into a single flat directory.
     
    .DESCRIPTION
    The `Merge-Directory` function flattens the structure of a specified directory by moving all files from subdirectories into the root of the specified directory. It supports confirmation prompts and can be forced to skip confirmation.
     
    .PARAMETER Directory
    The path to the directory to merge. This parameter is mandatory.
     
    .PARAMETER Force
    Skips the confirmation prompt and forces the merge operation.
     
    .EXAMPLE
    Merge-Directory -Directory "C:\MyFolder"
     
    Flattens the structure of "C:\MyFolder" by moving all files from subdirectories into the root of "C:\MyFolder" with confirmation.
     
    .EXAMPLE
    Merge-Directory -Directory "C:\MyFolder" -Force
     
    Flattens the structure of "C:\MyFolder" by moving all files from subdirectories into the root of "C:\MyFolder" without confirmation.
#>

    [CmdletBinding(HelpUri = 'https://steviecoaster.github.io/Pagootle/Commands/Merge-Directory', ConfirmImpact = 'High', SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory)]
        [ValidateScript({ Test-Path $_ })]
        [String]
        $Directory,

        [Parameter()]
        [Switch]
        $Force
    )

    end {
        if ($Force -and -not $Confirm) {
            $ConfirmPreference = 'None'
            if ($PSCmdlet.ShouldProcess("$_", 'Merge (flatten) Directory')) {
                Push-Location $Directory
                Get-ChildItem -Recurse -File | ForEach-Object {
                    Move-Item $_.FullName $pwd
                }
                Pop-Location
            }
        }
        else {
            if ($PSCmdlet.ShouldProcess("$_", 'Merge (flatten) Directory')) {
                Push-Location $Directory
                Get-ChildItem -Recurse -File | ForEach-Object {
                    Move-Item $_.FullName $pwd
                }
                Pop-Location
            }
        }
    }
}
#EndRegion '.\public\Utility\Merge-Directory.ps1' 59
#Region '.\public\Utility\Set-ProGetSSLConfig.ps1' -1

function Set-ProGetSslConfig {
    <#
  .SYNOPSIS
  Updates the ProGet configuration file and restarts the ProGet services.
   
  .DESCRIPTION
  This script updates the ProGet configuration file with specified parameters, handles SSL certificate settings, and restarts the ProGet services. It processes parameters, updates the XML configuration file, and ensures the correct attributes are set.
   
  .PARAMETER ConfigFile
  Specifies the path to the ProGet configuration file. Default is 'C:\ProgramData\Inedo\SharedConfig\ProGet.config'.
   
  .PARAMETER Location
  Specifies the location of the certificate store. Valid values are 'User' and 'LocalMachine'.
   
  .PARAMETER Store
  Specifies the certificate store. Valid values are 'My', and 'Root'.
   
  .PARAMETER Subject
  Specifies the subject name of the certificate.
   
  .PARAMETER AllowInvalid
  A switch parameter that allows invalid certificates if specified.
   
  .PARAMETER CertPassword
  Specifies the password for the certificate.
   
  .PARAMETER Urls
  Specifies the URLs for the web server to bind too.
   
  .EXAMPLE
  Set-ProGetSslConfig -Location 'Machine' -Store 'My' -Subject 'example.com' -AllowInvalid -CertPassword 'password' -Urls 'http://*:8624/'
   
  This command updates the ProGet configuration to use a certificate with the subject 'example.com' from the specified Windows certificate store.
   
  .EXAMPLE
  Set-ProGetSslConfig -CertFile "C:\proget_cert\cert.pfx" -CertPassword ('poshacme' | ConvertTo-SecureString -AsPlainText -Force) -Urls http://*:8624/,https://*:8443/
   
  Updates ProGet configuration to use the provided pfx file and pfx password to secure the ProGet instance. Also allows for non-http binding to port 8624
   
  .EXAMPLE
  Set-ProGetSslConfig -CertFile "C:\proget_cert\cert.pem" -KeyFile "C:\proget_cert\cert.key" -Urls http://*:8624/,https://*:8443/
   
  Updates ProGet configuration to use the provided pem and key file to secure the ProGet instance with SSL
   
  .NOTES
   
  #>

    [CmdletBinding(HelpUri = 'https://steviecoaster.github.io/Pagootle/Commands/Set-ProGetSslConfig')]
    Param(
        [Parameter(ParameterSetName = 'WindowsStore')]
        [Parameter(ParameterSetName = 'CertFile')]
        [Parameter(ParameterSetName = 'Pfx')]
        [String]
        $ConfigFile = 'C:\ProgramData\Inedo\SharedConfig\ProGet.config',
  
        [Parameter(Mandatory, ParameterSetName = 'WindowsStore')]
        [ValidateSet('User', 'LocalMachine')]
        [String]
        $Location,
  
        [Parameter(Mandatory, ParameterSetName = 'WindowsStore')]
        [ValidateSet('TrustedPeople', 'My', 'WebHosting')]
        [String]
        $Store,
  
        [Parameter(Mandatory, ParameterSetName = 'WindowsStore')]
        [String]
        $Subject,
  
        [Parameter(ParameterSetName = 'WindowsStore')]
        [Switch]
        $AllowInvalid,
  
        [Parameter(Mandatory, ParameterSetName = 'CertFile')]
        [Parameter(Mandatory,ParameterSetName = 'Pfx')]
        [ValidateScript({ Test-Path $_ })]
        [String]
        $CertFile,
  
        [Parameter(ParameterSetName = 'Pfx')]
        [SecureString]
        $CertPassword,
  
  
        [Parameter(ParameterSetName = 'CertFile')]
        [ValidateScript({ Test-Path $_ })]
        [String]
        $KeyFile,
  
        [Parameter(ParameterSetName = 'CertFile')]
        [Parameter(ParameterSetName = 'Pfx')]
        [Parameter(ParameterSetName = 'WindowsStore')]
        [ValidateScript({
            $_ | Foreach-object {
                if($psitem.EndsWith('/')){
                    $true
                } else {
                    throw "Url must end with a trailing '/'!"
                }
  
            }
        })]
        [String[]]
        $Urls = 'http://*:8624/'
  
    )
  
    end {
  
        #Update the configuration file
        [xml]$xml = Get-Content $ConfigFile
        $webServerNode = $xml.InedoAppConfig.WebServer
  
        #Set some defaults
        $webServerNode.attributes.RemoveAll()
        $webServerNode.SetAttribute('Enabled',$true)
  
        switch($PSCmdlet.ParameterSetName){
            'WindowsStore' {
                $PSBoundParameters.GetEnumerator() | ForEach-Object {
    
                    if ($_.Key -eq 'AllowInvalid') {
                        $webServerNode.SetAttribute($($_.Key), $([bool]$_.Value))
                    }
                    else {
                        $webServerNode.SetAttribute($_.Key, $_.Value)
                    }
                }
        
                if (-not $PSBoundParameters.ContainsKey('AllowInvalid')) {
                    $webServerNode.SetAttribute('AllowInvalid', $false)
                } else {
                    $webServerNode.SetAttribute('AllowInvalid',$AllowInvalid)
                }
                
                $certificateThumbprint = (Get-ChildItem Cert:\$($Location)\$($Store) | Where-Object {$_.Subject -like "CN=$Subject"}).Thumbprint
                $ServiceUser = (Get-CimInstance Win32_Service -Filter "Name = 'INEDOPROGETWEBSVC'").StartName

                Set-CertPermissions -Thumbprint $certificateThumbprint -Location $Location -Store $Store -ServiceUser $ServiceUser
            }
  
            'CertFile' {
                $PSBoundParameters.GetEnumerator() | ForEach-Object {
                    if(-not ($_.Key -eq 'ConfigFile')){
                        $webServerNode.SetAttribute($_.Key,$_.Value)
                    }
                }
            }
  
            'Pfx' {
                $PSBoundParameters.GetEnumerator() | ForEach-Object {
                    if(-not ($_.Key -eq 'ConfigFile')){
                        if($_.Key -eq 'CertPassword'){
                            $tempCred = [System.Management.Automation.PSCredential]::new('toss',$CertPassword)
                            $CleartextPassword = $tempCred.GetNetworkCredential().Password
                            $webServerNode.SetAttribute('Password',$CleartextPassword)
                        } else {
                            $webServerNode.SetAttribute($_.Key,$_.Value)
                        }
                    }
                }
  
                if($webServerNode.HasAttribute('KeyFile')){
                    $webServerNode.RemoveAttribute('KeyFile')
                }
  
            }
        }
  
        #Handle port bindings
        if($urls){
            if($Urls.Count -gt 1){
                $webServerNode.SetAttribute('Urls',$($Urls -join ';'))
            }
            else {
                $webServerNode.SetAttribute('Urls',$Urls)
            }
        }
  
        #Write the config file
        $xml.Save($ConfigFile)

        #Update the permissions on the certificate private key, to give service user READ permissions
  
        #Restart the ProGet services
        Get-Service inedoproget* | Restart-Service
    }
  }
#EndRegion '.\public\Utility\Set-ProGetSSLConfig.ps1' 189
#Region '.\Suffix.ps1' -1

#EndRegion '.\Suffix.ps1' 1