PowerShell.PowerLibrary.Core.psm1

#region Get-UniqueId
FUNCTION Get-UniqueId
{
    <#
    .Synopsis
        Generates Guid from plain text.
    .DESCRIPTION
        Use this method to Generates GUID from any input value. The Guid is always unique per input value.
    .PARAMETER InputValue
        Any Non Null Value String
    .EXAMPLE
        $Id = Get-UniqueId 'Any Text Can Go Here! :)';
    #>


    param
    (
        [ValidateNotNullOrEmpty()]
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [string]
        $InputValue
    );

    $random = [System.Random]::new($InputValue.GetHashCode());
    $guidBytes = New-Object byte[] 16;
    $random.NextBytes($guidBytes);
    return [Guid]::new($guidBytes).ToString();
}
#endregion

#region Write-Message
FUNCTION Write-Message
{
    <#
    .Synopsis
        Write Message with Silent Option.
    .DESCRIPTION
        Same as Write-Host except that you may set a switch (Silent) so message won't be sent to the Host.
    .PARAMETER Object
        Any Object
    .PARAMETER Separator
        Separator Object
    .PARAMETER NoNewline
        Switch not to go to a new Line
    .PARAMETER ForegroundColor
        As the name says :) Fore Color
    .PARAMETER BackgroundColor
        As the name says :) Background Color
    .PARAMETER Silent
        Switch to toggle whether to write the message or not.
    .EXAMPLE
        Write-Message "Test" -Silent:$true;
    #>


    #region Parameters
    [CmdletBinding()]
    param 
    (
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [object]
        $Object,

        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [object]
        $Separator,

        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [switch]
        $NoNewline = $false,
        
        [ValidateSet('Black', 'DarkBlue', 'DarkGreen', 'DarkCyan', 'DarkRed', 'DarkMagenta', 'DarkYellow', 'Gray', 'DarkGray', 'Blue', 'Green', 'Cyan', 'Red', 'Magenta', 'Yellow', 'White')]
        [ConsoleColor]
        $ForegroundColor,
        
        [ValidateSet('Black', 'DarkBlue', 'DarkGreen', 'DarkCyan', 'DarkRed', 'DarkMagenta', 'DarkYellow', 'Gray', 'DarkGray', 'Blue', 'Green', 'Cyan', 'Red', 'Magenta', 'Yellow', 'White')]
        [ConsoleColor]
        $BackgroundColor,

        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [switch]
        $Silent = $false
    )
    #endregion

    BEGIN
    {
        IF($ForegroundColor -eq $null)
        {
            $ForegroundColor = [ConsoleColor]::Green;
        }
        IF($BackgroundColor -eq $null)
        {
            $BackgroundColor = [ConsoleColor]::Black;
        }
    }

    PROCESS
    {
        IF(!$Silent)
        {
            Write-Host $Object -NoNewline:$NoNewline -Separator $Separator -ForegroundColor $ForegroundColor -BackgroundColor $BackgroundColor;
        }
    }

    END
    {

    }
}
#endregion

#region Write-Line
FUNCTION Write-Line
{    
    <#
    .Synopsis
        Write Line with Silent Option.
    .DESCRIPTION
        Same as Write-Host except that you may set a switch (Silent) so message won't be sent to the Host.
        This Method Draws a Line.
    .PARAMETER Object
        Any Object
    .PARAMETER Separator
        Separator Object
    .PARAMETER NoNewline
        Switch not to go to a new Line
    .PARAMETER ForegroundColor
        As the name says :) Fore Color
    .PARAMETER BackgroundColor
        As the name says :) Background Color
    .PARAMETER Silent
        Switch to toggle whether to write the message or not.
    .EXAMPLE
        Write-Line;
    #>


    #region Parameters
    [CmdletBinding()]
    param 
    (
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [string]
        $Object = '*',

        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [string]
        $Separator,
        
        [ValidateSet('Black', 'DarkBlue', 'DarkGreen', 'DarkCyan', 'DarkRed', 'DarkMagenta', 'DarkYellow', 'Gray', 'DarkGray', 'Blue', 'Green', 'Cyan', 'Red', 'Magenta', 'Yellow', 'White')]
        [ConsoleColor]
        $ForegroundColor,
        
        [ValidateSet('Black', 'DarkBlue', 'DarkGreen', 'DarkCyan', 'DarkRed', 'DarkMagenta', 'DarkYellow', 'Gray', 'DarkGray', 'Blue', 'Green', 'Cyan', 'Red', 'Magenta', 'Yellow', 'White')]
        [ConsoleColor]
        $BackgroundColor,

        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [switch]
        $Silent = $false
    )
    #endregion

    BEGIN
    {
        IF($ForegroundColor -eq $null)
        {
            $ForegroundColor = [ConsoleColor]::Green;
        }
        IF($BackgroundColor -eq $null)
        {
            $BackgroundColor = [ConsoleColor]::Black;
        }
        $Size = $(Get-Host).UI.RawUI.BufferSize.Width;

        IF([string]::IsNullOrEmpty($Object))
        {
            $Object = '*';
        }

        IF([string]::IsNullOrEmpty($Separator))
        {
            $Separator = [string]::Empty;
        }
    }

    PROCESS
    {
        Write-Message ([string]::Join($Separator, [System.Linq.Enumerable]::Repeat($Object, $Size))) -NoNewline -ForegroundColor $ForegroundColor -BackgroundColor $BackgroundColor -Silent:$Silent;
    }

    END
    {
        Write-Message -Silent:$Silent;;
    }
}
#endregion

#region Log-Error
FUNCTION Trace-Error([string]$Message, [System.Exception]$Exception)
{
    <#
    .Synopsis
        A wrapper to throw Exception to the Host.
    .DESCRIPTION
        A wrapper to throw Exception to the Host.
    .EXAMPLE
        $Id = Get-UniqueId 'Any Text Can Go Here! :)';
    #>

    throw $Message;
}
Set-Alias -Name Log-Error -Value Trace-Error;
#endregion

#region Get-PartialFileFullName
FUNCTION Get-PartialFileFullName
{    
    <#
    .Synopsis
        Returns the Full Path of a given Partial Script File Name.
    .DESCRIPTION
        Returns the Full Path of a given Partial Script File Name.
    .PARAMETER FullName
        FullName can be full qualified Url or partial Url Suffix.
    .EXAMPLE
        $Id = Get-UniqueId 'Any Text Can Go Here! :)';
    #>


    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline=$true)]
        [Alias('V')]
        [ValidateNotNullOrEmpty()]
        [string]
        $FullName
    );

    IF(![System.IO.Path]::HasExtension($FullName))
    {
        $FullName = [string]::Concat($FullName, '.ps1');
    }
    IF([System.IO.File]::Exists($MyInvocation.ScriptName))
    {
        $FileName = [System.IO.Path]::GetFileNameWithoutExtension($MyInvocation.ScriptName);
        $VariableDirectory = [System.IO.Path]::Combine($MyInvocation.PSScriptRoot, $FileName);
        IF([System.IO.Directory]::Exists($VariableDirectory))
        {
            $FullName = [System.IO.Path]::Combine($VariableDirectory, $FullName);
        }
    }
    IF(![System.IO.File]::Exists($FullName))
    {
        Log-Error "$FullName does not exist.";
    }
    return $FullName;
}
#endregion

#region Invoke-PostWebRequest
FUNCTION Invoke-PostWebRequest
{
    <#
    .Synopsis
        Wrapper to Invoke Post Web Request with Option to Retry.
    .DESCRIPTION
        Wrapper to Invoke Post Web Request with Option to Retry.
    .PARAMETER Url
        Post Url.
    .PARAMETER Payload
        Post Payload Body Object
    .PARAMETER RetryOnException
        A switch to set whether to retry the post on exception.
    .PARAMETER MaximumRetries
        Defines a maximum number of Retries
    .PARAMETER Timeout
        Default Value is 180.
        Timeout is in Seconds
    #>


    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline=$true)]
        [Alias('U')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Url,
        
        [Parameter(Mandatory = $false, ValueFromPipeline=$true)]
        [Alias('P')]
        [ValidateNotNullOrEmpty()]
        [object]
        $Payload,
        
        [Parameter(Mandatory = $false, ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [switch]
        $RetryOnException,
        
        [Parameter(Mandatory = $false, ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [uint64]
        $MaximumRetries,

        [Parameter(Mandatory = $false, ValueFromPipeline=$true)]
        [Alias('T')]
        [uint64]
        $Timeout = 180
    );
        
    $Retry = $false;
    $Retries = 0;
    DO
    {
        $Retries++;
        TRY
        {
            IF($Retry)
            {                
                Write-Host "Re-POSTing to $Url ..." -ForegroundColor Magenta;
            }
            ELSE
            {                
                Write-Host "POSTing to $Url ..." -ForegroundColor Gray;
            }
            $Response = Invoke-WebRequest -Uri $Url -Method POST -TimeoutSec $Timeout -Body $Payload;
            $Retry = $false;
        }
        CATCH [System.Net.WebException]
        {
            Write-Host $_.Exception.Message;
            Write-Host $_.Exception.Status;
            $Retry = $true;
        }
    }
    WHILE($RetryOnException -and $Retry -and $Retries -le $MaximumRetries);

    if($Response.StatusCode -ge 300)
    {
        Write-Output $Response
        Log-Error "Error occurred."
    }

    return $Response;
}
#endregion

#region Invoke-GetWebRequest
FUNCTION Invoke-GetWebRequest
{
    <#
    .Synopsis
        Wrapper to Invoke Get Web Request with Option to Retry.
    .DESCRIPTION
        Wrapper to Invoke Get Web Request with Option to Retry.
    .PARAMETER Url
        Get Url.
    .PARAMETER Payload
        Get Payload Body Object
    .PARAMETER RetryOnException
        A switch to set whether to retry the post on exception.
    .PARAMETER MaximumRetries
        Defines a maximum number of Retries
    .PARAMETER Timeout
        Default Value is 180.
        Timeout is in Seconds
    #>


    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline=$true)]
        [Alias('U')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Url,
        
        [Parameter(Mandatory = $false, ValueFromPipeline=$true)]
        [Alias('P')]
        [ValidateNotNullOrEmpty()]
        [object]
        $Payload,
        
        [Parameter(Mandatory = $false, ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [switch]
        $RetryOnException,
        
        [Parameter(Mandatory = $false, ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [uint64]
        $MaximumRetries,

        [Parameter(Mandatory = $false, ValueFromPipeline=$true)]
        [Alias('T')]
        [ValidateNotNullOrEmpty()]
        [uint64]
        $Timeout = 180
    );

    $Retry = $false;
    $Retries = 0;
    DO
    {
        $Retries++;
        TRY
        {
            IF($Retry)
            {                
                Write-Host "Re-GETing from $Url ..." -ForegroundColor Magenta;
            }
            ELSE
            {                
                Write-Host "GETing from $Url ..." -ForegroundColor Gray;
            }

            $Response = Invoke-WebRequest -Uri $Url -Method Get -TimeoutSec $Timeout -Body $Payload;
            $Retry = $false;
        }
        CATCH [System.Net.WebException]
        {
            Write-Host $_.Exception.Message;
            Write-Host $_.Exception.Status;
            $Retry = $true;
        }
    }
    WHILE($RetryOnException -and $Retry -and $Retries -le $MaximumRetries);


    if($Response.StatusCode -ge 300)
    {
        Write-Output $Response
        Log-Error "Error occurred."
    }

    return $Response;
}
#endregion

#region New-ZipArchive
FUNCTION New-ZipArchive
{
    <#
    .Synopsis
        Wrapper to Package Files Through 7-Zip.
    .DESCRIPTION
        Wrapper to Package Files Through 7-Zip.
    .PARAMETER Output
        This is the Path where to Save the Archived Package File.
    .PARAMETER Path
        This is the Path of Where the Archiver will package Files from.
    .PARAMETER ArchiveName
        As the name states, it is the Archive file Name (Without Extension).
    .PARAMETER SFX
        Switch to state whether the archive will be executable (Exe) or normal 7-zip Archive (7z).
    .PARAMETER DeleteAfterCompression
        Switch to state whether to Delete the Original (Path) Files after the process completes Archiving.
    .PARAMETER Override
        Switch to state the write mode of the Archiver.
    .PARAMETER Help
        Switch to Plot 7-zip Help.
    .EXAMPLE
        New-ZipArchive `
        -Override:$true `
        -SFX:$true `
        -DeleteAfterCompression:$true `
        -Path D:\Backup\Solutions\FolderToPackage `
        -Output D:\Backup\Solutions\ `
        -ArchiveName ArchivedSolutions `
        ;
    #>


    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline=$true)]
        [Alias('W')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Output,

        [Parameter(Mandatory = $true, ValueFromPipeline=$true)]
        [Alias('P')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Path,
        
        [Parameter(Mandatory = $false, ValueFromPipeline=$true)]
        [Alias('N')]
        [ValidateNotNullOrEmpty()]
        [string]
        $ArchiveName,
        
        [Parameter(Mandatory = $false, ValueFromPipeline=$true)]
        [Alias('EXE')]
        [switch]
        $SFX,

        [Parameter(Mandatory = $false, ValueFromPipeline=$true)]
        [Alias('D')]
        [switch]
        $DeleteAfterCompression,
        
        [Parameter(Mandatory = $false, ValueFromPipeline=$true)]
        [Alias('O')]
        [switch]
        $Override,
        
        [Parameter(Mandatory = $false, ValueFromPipeline=$true)]
        [Alias('H')]
        [switch]
        $Help
    );

    Write-Host "Archiving..." -ForegroundColor Cyan;
    
    $7Zip = "$env:ProgramFiles\7-Zip\7z.exe";

    IF (-not (Test-Path $7Zip)) { Log-Error "$7Zip needed"; }

    Set-Alias zipper $7Zip;

    IF($Help)
    {
        zipper;
    } 

    IF([string]::IsNullOrWhiteSpace($ArchiveName))
    {
        $ArchiveName = [System.IO.Path]::GetFileNameWithoutExtension($Output);
    }
    ELSE
    {
        $ArchiveName = [System.IO.Path]::GetFileNameWithoutExtension($ArchiveName)
    }

    $ArchiveName = [System.IO.Path]::Combine($Output, $ArchiveName);

    $Command = "zipper a -t7z -bt -r -o'$Output'*";
    IF($SFX.IsPresent)
    {
        $Command += " -sfx";
        $Extension += ".exe";
    }
    ELSE
    {
        $Extension += ".7z";
    }

    IF($DeleteAfterCompression.IsPresent)
    {
        $Command += " -sdel";
    }

    IF(![System.IO.Path]::HasExtension($ArchiveName))
    {
        $ArchiveName += $Extension;
    }

    IF($Override -and [System.IO.File]::Exists($ArchiveName))
    {
        Remove-Item -Path $ArchiveName -Confirm:$false -Force;
    }

    $Command += " '$ArchiveName' '$Path'";

    Write-Host "Path: " -NoNewline -ForegroundColor Yellow;
    Write-Host $Path -ForegroundColor White;
    Write-Host "Archive Name: " -NoNewline -ForegroundColor Yellow;
    Write-Host $ArchiveName -ForegroundColor White;

    &([scriptblock]::create($Command));
}
#endregion

#region Get-ZipArchive
FUNCTION Get-ZipArchive
{
    <#
    .Synopsis
        Wrapper to Extract Package Files Through 7-Zip.
    .DESCRIPTION
        Wrapper to Extract Package Files Through 7-Zip.
    .PARAMETER Output
        This is the Path where to Extract the Archived Package File.
    .PARAMETER Path
        This is the Path of the Archive (FullName with Extension).
    .PARAMETER Override
        Switch to state the write mode of the Archiver.
    .PARAMETER Help
        Switch to Plot 7-zip Help.
    #>


    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline=$true)]
        [Alias('W')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Output,
        
        [Parameter(Mandatory = $false, ValueFromPipeline=$true)]
        [Alias('P')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Path,
        
        [Parameter(Mandatory = $false, ValueFromPipeline=$true)]
        [Alias('O')]
        [ValidateNotNullOrEmpty()]
        [switch]
        $Override,
        
        [Parameter(Mandatory = $false, ValueFromPipeline=$true)]
        [Alias('H')]
        [ValidateNotNullOrEmpty()]
        [switch]
        $Help
    );
    
    $7Zip = "$env:ProgramFiles\7-Zip\7z.exe";

    IF (-not (Test-Path $7Zip)) { Log-Error "$7Zip needed"; }

    Set-Alias zipper $7Zip;

    IF($Help)
    {
        zipper;
    } 

    $Command = "zipper x -bt -o'$Output'*";

    IF($Override)
    {
        $Command += " -y";
    }

    IF(![System.IO.Path]::HasExtension($Path))
    {
        Log-Error "$Path is not a file or has no extension."
    }

    $Command += " '$Path'";

    &([scriptblock]::create($Command));
}
#endregion

#region Confirm-Directory
FUNCTION Confirm-Directory
{
    <#
    .Synopsis
        Ensures directory Exists.
    .DESCRIPTION
        Ensures directory Exists.
        If NOT, it will be created...
    .PARAMETER Path
        This is the Directory Full Name.
    #>


    [CmdletBinding()]
    param 
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [Alias('P')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Path
    )
    
    IF(![System.IO.Directory]::Exists($Path))
    {
        [System.IO.Directory]::CreateDirectory($Path);
    }
    RETURN $Path;
}
#endregion

#region Join-Uri
FUNCTION Join-Uri
{
    <#
    .Synopsis
        Simply Builds a URL.
    .DESCRIPTION
        Simply Builds a URL
    .PARAMETER BaseUrl
        This is the URL Base Address.
    .PARAMETER UriParts
        This is the URL Parts to be amended.
    .EXAMPLE
        $PostUrl = Join-Uri -B "https://baseaddress" -A "/ApplicationName", "/api/Notification/CreateQueuesIfNotExist";
    #>

    param
    (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, Position = 0, HelpMessage='This parameter represents the base URI')]
        [Alias('B')]
        [ValidateNotNullOrEmpty()]
        [string]
        $BaseUrl,

        [Parameter(Mandatory = $false, ValueFromPipeline = $true, Position = 1)]
        [Alias('A')]
        [string[]]
        $UriParts
    );
    $uri = [System.Text.StringBuilder]::new($BaseUrl.TrimEnd("/"));
    IF($UriParts.Length -eq 0) { RETURN $uri.ToString(); }
    FOREACH ($part in $UriParts)
    {
        $uri.AppendFormat("/{0}", $part.TrimEnd("/").TrimStart("/")) | Out-Null;
    }
    RETURN $uri.ToString();
}
#endregion

#region Get Dynamic Parameters
FUNCTION Get-DynamicParameters 
{
    <#
    .Synopsis
        Generate dynamic parameters
    .DESCRIPTION
        Returns the Common Parameters for all Public Functions.
    #>

    [CmdletBinding()]param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [string]
        $CommandName,
        
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, Position = 1)]
        [string[]]
        $ExcludedProperties,
        
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [switch]
        $IncludeDefaultValue
    );

    BEGIN 
    {        
        $DefaultParametersNames = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([type][System.Management.Automation.Internal.CommonParameters]) | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name;
        $Command = $(Get-Command $CommandName);
        $AST = $Command.ScriptBlock.Ast;
        $Parameters = $Command.Parameters.GetEnumerator();
        $WritableParamAttributePropertyNames = [System.Management.Automation.ParameterAttribute]::new() | Get-Member -MemberType Property | Where-Object { $_.Definition -match "{.*set;.*}$" } | Select-Object -ExpandProperty Name;
        # Create the Runtime Param dictionary
        $DynamicParameterDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new();
    }

    PROCESS 
    {
        FOREACH ($Parameter in $Parameters) 
        {
            IF(($DefaultParametersNames -contains $Parameter.Key) -or (@('WhatIf', 'Confirm') -contains $Parameter.Key) -or ($ExcludedProperties -contains $Parameter.Key)) { continue; }
            
            $AttributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new();

            $ParameterType = $Parameter.Value.ParameterType;

            FOREACH ($CurrentAttribute in $Parameter.Value.Attributes) 
            {
                $AttributeTypeName = $CurrentAttribute.TypeId.FullName;

                switch -wildcard ($AttributeTypeName) 
                {
                    System.Management.Automation.ArgumentTypeConverterAttribute 
                    {
                        continue;  # So blank param doesn't get added
                    }

                    System.Management.Automation.Validate*Attribute 
                    {
                        $NewParameterAttribute = $CurrentAttribute;
                        $AttributeCollection.Add($NewParameterAttribute);
                    }

                    System.Management.Automation.ParameterAttribute 
                    {

                        $NewParameterAttribute = [System.Management.Automation.ParameterAttribute]::new();
                        
                        FOREACH ($PropertyName in $WritableParamAttributePropertyNames) 
                        {
                            IF ($NewParameterAttribute.$PropertyName -ne $CurrentAttribute.$PropertyName) 
                            {  
                                # nulls cause an error if you assign them to some of the properties
                                $NewParameterAttribute.$PropertyName = $CurrentAttribute.$PropertyName;
                            }
                        }

                        $NewParameterAttribute.ParameterSetName = $CurrentAttribute.ParameterSetName;

                        $AttributeCollection.Add($NewParameterAttribute);
                    }

                    default 
                    {
                        Write-Warning "Function doesn't handle dynamic param copying for $AttributeTypeName";
                        continue;
                    }
                }
            }

            $DynamicParameter = [System.Management.Automation.RuntimeDefinedParameter]::new($Parameter.Key, $ParameterType, $AttributeCollection);
            $DynamicParameterDictionary.Add($Parameter.Key, $DynamicParameter);
        }
        IF($IncludeDefaultValue.IsPresent)
        {
            FOREACH ($Parameter in $AST.ParamBlock.Parameters.GetEnumerator())
            {
                if(!$DynamicParameterDictionary.TryGetValue($Parameter.Name.VariablePath.UserPath, [ref]$DynamicParameter)){ continue; }
                if($null -eq $Parameter.DefaultValue) { continue; }
                $DynamicParameter.Value = $Parameter.DefaultValue.Value;
            }
        }
        # Return the dynamic parameters
        RETURN $DynamicParameterDictionary;
    }
}
#endregion