core.psm1

<#
        .Synopsis
        Module that extends core functionality in powershell.
 
        .DESCRIPTION
        Module with various generic functions that could be used in any script
 
        .NOTES
        Code reuse saves time!
 
        .COMPONENT
        Core
 
        .ROLE
        Fill gaps in general use
 
        .FUNCTIONALITY
        General Powershell functionality extension
 
        .AUTHOR
        Chris Masters - https://github.com/masters274
#>



#region Custom Shared Types

$sbMkLinkType = {
    <#
            lpSymlinkFileName = [String] Path/name where you wan the link to be placed
            lpTargetFileName = [String] File or folder path you want linked to
            dwFlags = [int] 0 for file, and 1 for directory. Use logic to figure this out instead of asking
    #>


    $typDef = @'
        using System;
        using System.Runtime.InteropServices;
 
        namespace mklink
        {
            public class symlink
            {
                [DllImport("kernel32.dll", EntryPoint="CreateSymbolicLink")]
                public static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, int dwFlags);
 
                [DllImport("kernel32.dll", EntryPoint="CreateHardLink")]
                public static extern bool CreateHardLink(string lpSymlinkFileName, string lpTargetFileName, IntPtr lpSecurityAttributes);
            }
        }
'@

    Try {
        $null = [mklink.symlink]
    }

    Catch {
        Add-Type -TypeDefinition $typDef
    }
}


& $sbMkLinkType

#endregion


#region : DEVELOPMENT FUNCTIONS


Function Test-ModuleLoaded {
    <#
            .SYNOPSIS
            Checks that all required modules are loaded.
 
            .DESCRIPTION
            Receives an array of strings, which should be the module names.
            The function then checks that these are loaded. If the required
            modules are not loaded, the function will try to load them by name
            via the default module path. Function returns a failure if it's
            unable to load any of the required modules.
 
            .PARAMETER RequiredModules
            Parameter should be a string or array of strings.
 
            .PARAMETER Quiet
            Avoids output to the screen.
 
            .EXAMPLE
            Test-ModuleLoaded -RequiredModules "ActiveDirectory"
            Verifies that the ActiveDirectory module is loaded. If not, it will attempt to load it.
            if this fails, a $false will be returned, otherwise, a $true will be returned.
 
            $arrayModules = ('ActiveDirectory','MyCustomModule')
            $result = Test-ModuleLoaded -RequiredModules $arrayModules
 
            Checks if the two modules are loaded, or loadable, if so, $result will contain a value of
            $true, otherwise it will contain the value of $false.
 
            .NOTES
            None yet.
 
            .LINK
            https://github.com/masters274/
 
            .INPUTS
            Requires at the very least, a string name of a module.
 
            .OUTPUTS
            Returns success or failure code ($true | $false), depending on if required modules are loaded.
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true, HelpMessage = 'String array of module names')]
        [String[]]$RequiredModules,
        [Switch]$Quiet
    )


    Process {
        # Variables
        $boolDebug = $PSBoundParameters.Debug.IsPresent
        $loadedModules = Get-Module
        $availableModules = Get-Module -ListAvailable
        [int]$failedModules = 0
        [System.Collections.ArrayList]$missingModules = @()
        $arraryRequiredModules = $RequiredModules

        # Loop thru all module requirements
        Foreach ($module in $arraryRequiredModules) {
            Invoke-DebugIt -Message 'Module' -Value $module -Console

            IF ($loadedModules.Name -contains $module) {
                $true | Out-Null
            }

            ElseIF (($availableModules.Name -ccontains $module) -or ($null = Test-Path -Path $module)) {
                Import-Module -Name $module
            }

            Else {
                Invoke-DebugIt -Message 'Missing module' -Value $module -Console

                $missingModules.Add($module)
                $failedModules++
            }
        }

        # Return the boolean value for success for failure
        if ($failedModules -gt 0) {
            Write-Error -Message 'Failed to load required modules'
        }

        else {
            IF (!($Quiet)) {
                return $true
            }
        }
    }
}


Function Invoke-VariableBaseLine {
    <#
            .SYNOPSIS
            A function used to keep your environment clean.
 
            .DESCRIPTION
            This function, when used at the beginning of a script or major setup of functions, will snapshot
            the variables within the local scope. when ran for the second time with the -Clean parameter, usually
            at the end of a script, will remove all the variables created during the script run. This is helpful
            when working in ISE and you need to run your script multiple times while building. You don't want
            prexisting data to end up in the second run. Also when you have an infinite loop script that you need
            the environment clean after each call to something.
 
            .PARAMETER Clean
            The name says it all...
 
            .EXAMPLE
            Invoke-VarBaseLine -Clean
            This will clean up all the variables created between the start and finish callse of this function
 
            .NOTES
            This ain't rocket surgery :-\
 
            .LINK
            https://github.com/masters274/
 
            .INPUTS
            N/A.
 
            .OUTPUTS
            Void.
    #>




    [CmdletBinding()]
    Param
    (
        [Switch]$Clean
    )

    Begin {
        if ($Clean -and -not $baselineLocalVariables) {
            Write-Error -Message 'No baseline variable is set to revert to.'
        }
    }

    Process {
        # logger -Console -Force -Value $(($MyInvocation.Line).split(' ')[1]).Trim()

        if ($Clean) {
            Compare-Object -ReferenceObject $($baselineLocalVariables.Name) -DifferenceObject `
            $((Get-Variable -Scope 0).Name) |
            Where-Object { $_.SideIndicator -eq '=>' } |
            ForEach-Object {
                Remove-Variable -Name ('{0}' -f $_.InputObject) -ErrorAction SilentlyContinue
            }
        }

        else {
            $Global:baselineLocalVariables = Get-Variable -Scope Local
        }
    }

    End {
        if ($Clean) {
            Remove-Variable -Name baselineLocalVariables -Scope Global -ErrorAction SilentlyContinue
        }
    }
}


Function Add-Signature {
    # Signs a file using the first code signing cert in your personal store
    # ./makecert -n "PowerShell Local CertificateRoot" -a sha1 -eku 1.3.6.1.5.5.7.3.3 -r -sv root.pvk root.cer -ss Root -sr localMachine
    # ./makecert -n "PowerShell tux" -ss MY -a sha1 -eku 1.3.6.1.5.5.7.3.3 -iv root.pvk -ic root.cer

    Param
    (
        [string] $File = $(throw "Please specify a filename.")
    )

    # $cert = @(Get-ChildItem cert:\CurrentUser\My | where-object { $_.FriendlyName -eq "MyCodeSigningCert" }) #[0] #-codesigning)[0]
    $cert = (Get-ChildItem Cert:currentuser\my\ -CodeSigningCert |
        Select-Object -First 1)

    # check if the file is a PowerShell file, if not, fix it...
    $srtExt = ( Get-ChildItem -Path $File |
        ForEach-Object { $_.Extension } )

    IF ($srtExt -ne '.ps1') {
        # we want to be able to sign any file that we can write to...

        # rename the file
        Get-ChildItem -Path $File | Rename-Item -NewName { $_.Name -replace "$srtExt$" , ".ps1" }

        # get the temporary file name
        $strTempName = [io.path]::ChangeExtension($File, "ps1")

        # sign the file with the new name
        Set-AuthenticodeSignature $strTempName $cert

        # change the file name back to the original
        Get-ChildItem -Path $strTempName | Rename-Item -NewName { $_.Name -replace ".ps1$" , "$srtExt" }
    }

    Else {
        # just sign the file...
        Set-AuthenticodeSignature $File $cert
    }
}


Function Invoke-EnvironmentalVariable {
    <#
            .Synopsis
            Short description
 
            .DESCRIPTION
            Long description
 
            .EXAMPLE
            Example of how to use this cmdlet
 
            .EXAMPLE
            Another example of how to use this cmdlet
    #>


    <#
            Version 0.1
            - Day one, it's my berphday!
    #>


    [CmdLetBinding()]
    [CmdletBinding(DefaultParameterSetName = 'Get')]
    Param
    (
        [Parameter(Mandatory = $true, Position = 0,
            HelpMessage = 'Name of the variable')]
        [String] $Name,

        [Parameter(Position = 1,
            HelpMessage = 'Value of the variable')]
        $Value,

        [Parameter(Mandatory = $false, Position = 2,
            HelpMessage = 'Select the scope you require.')]
        [ValidateSet('Machine', 'User', 'Process')]
        [String]$Scope = 'User',

        [ValidateSet('Get', 'Set', 'Remove', 'New')]
        [String]$Action = 'Get'
    )

    Begin {
        # Baseline our environment
        #Invoke-VariableBaseLine

        # Debugging for scripts
        [Bool] $boolDebug = $PSBoundParameters.Debug.IsPresent
    }

    Process {
        # Variables
        [String] $strCommand = '[Environment]::GetEnvironmentVariable($Name,$Scope)'
        [String] $strFunctionCalledName = $MyInvocation.InvocationName
        [Bool] $boolIsAdmin = Test-AdminRights

        Invoke-DebugIt -Message 'Command text' -Value $strCommand -Console
        Invoke-DebugIt -Message 'Function called name' -Value $strFunctionCalledName -Console
        Invoke-DebugIt -Message 'Admin?' -Value $boolIsAdmin -Console
        Invoke-DebugIt -Message 'first item in command pipe' -Value $MyInvocation.InvocationName -Console

        IF (
            $Action -eq 'Set' -or $Action -eq 'New' -or `
                $strFunctionCalledName -eq 'Set-EnvVar' -or `
                $strFunctionCalledName -eq 'Set-EnvironmentalVariable' -or `
                $strFunctionCalledName -eq 'New-EnvVar' -or `
                $strFunctionCalledName -eq 'New-EnvironmentalVariable'
        ) {
            $Action = 'Set' # setting casue the default is get, and messes with logic later.
            IF ($Value) {
                [String] $strCommand = '[Environment]::SetEnvironmentVariable("{0}","{1}","{2}")' -f `
                    $Name, $Value, $Scope
            }

            Else {
                Write-Error -Message ('{0} : Value is required when using "Set"' -f $strFunctionCalledName)
                Return
            }
        }

        ElseIF (
            $Action -eq 'Remove' -or `
                $strFunctionCalledName -match 'Remove-EnvVar' -or `
                $strFunctionCalledName -match 'Remove-EnvironmentalVariable'
        ) {
            $Action = 'Remove' # setting casue the default is Get, and messes with logic later.
            [String] $strCommand = 'Remove-Item -Path Env:\{0}' -f $Name
        }

        IF ($boolIsAdmin -or ($Scope -eq 'User' -or $Scope -eq 'Process' -or $Action -eq 'Get')) {
            Invoke-Expression -Command $strCommand
        }

        Else {
            Invoke-Elevate -Command $strCommand -Persist
        }
    }

    End {
        # Clean up the environment
        #Invoke-VariableBaseLine -Clean
    }
}


Function ConvertTo-Hexadecimal {
    Param
    (
        [ValidateScript({ Test-Path -Path $_ -PathType 'Leaf' })]
        [String] $FilePath
    )

    # Converts a file to hexadecimal string.

    [byte[]] $hex = Get-Content -Encoding byte -Path $FilePath # C:\path\to\file.exe
    # [System.IO.File]::WriteAllLines(".\hexdump.txt", ([string]$hex)) # Ouput HEX to file

    [String] $hex
}


Function ConvertFrom-Hexadecimal {
    # Converts hexadecimal string to ASCII.
    Param
    (
        [String]$HexString
    )

    # Variables
    $Encoder = [System.Text.Encoding]::ASCII

    [Byte[]] $strTemp = $HexString -Split ' '

    $Encoder.GetString($strTemp)
}


Function ConvertFrom-HexToFile {
    # Converts hexadecimal string to file.
    # PS > [byte[]] $hex = gc -encoding byte -path C:\path\to\file.exe
    # PS > [System.IO.File]::WriteAllBytes(".\hexdump.txt", ([string]$hex))

    Param
    (
        [String]$HexString,

        [ValidateScript({ Split-Path $_ -Parent | Test-Path })]
        [String] $FilePath
    )

    # Variables
    $strfilename = $FilePath | Split-Path -Leaf

    Try {
        #$objDirectory = gci ($FilePath | Split-Path -Parent)

        $strDirectory = (Get-Item -Path $($FilePath | Split-Path -Parent)).FullName
    }

    Catch {
        $strDirectory = $pwd.Path
    }

    $file = "$strDirectory\$strfilename"

    [Byte[]] $strTemp = $HexString -Split ' '

    [System.IO.File]::WriteAllBytes($file, $strTemp) # NOTE: MUST BE FULL FILE PATH!
}


Function ConvertFrom-Base36 {
    Param
    (
        [Parameter(valuefrompipeline = $true,
            HelpMessage = 'Alphadecimal string to convert')]
        [string] $Base36Num = ''
    )

    $alphabet = "0123456789abcdefghijklmnopqrstuvwxyz"
    $inputarray = $base36Num.tolower().tochararray()
    [array]::reverse($inputarray)
    [long]$decNum = 0
    $pos = 0

    foreach ($c in $inputarray) {
        $decNum += $alphabet.IndexOf($c) * [long][Math]::Pow(36, $pos)
        $pos++
    }
    $decNum
}


Function ConvertTo-Base36 {
    Param
    (
        [Parameter(valuefrompipeline = $true,
            HelpMessage = 'Integer number to convert')]
        [int] $DecNum
    )

    $alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"

    Do {
        $remainder = ($DecNum % 36)
        $char = $alphabet.substring($remainder, 1)
        $base36Num = "$char$base36Num"
        $DecNum = ($DecNum - $remainder) / 36
    }
    While ($DecNum -gt 0)

    $base36Num
}


Function ConvertFrom-Base64 {
    Param
    (
        [String] $InputString
    )

    $bytes = [System.Convert]::FromBase64String($InputString)
    $decoded = [System.Text.Encoding]::UTF8.GetString($bytes)

    $decoded
}


Function ConvertTo-Base64 {
    Param
    (
        [String] $InputString
    )
    $bytes = [System.Text.Encoding]::UTF8.GetBytes($InputString)
    $encoded = [System.Convert]::ToBase64String($bytes)

    $encoded
}


Function Convert-ByteArrayToHex {
    Param
    (
        $ByteArray
    )

    If ($ByteArray.GetType().Name -eq 'Byte[]') {
        [String] $Bytes = $ByteArray -Join ' '
    }
    Else {
        $Bytes = $ByteArray
    }

    [String] $strReturnValue = $null

    ForEach ($Byte In $Bytes.ToString().Split(' ') ) {
        $strReturnValue += '0x' + [Convert]::ToString($Byte, 16).ToUpper().PadLeft(2, '0') + ','
    }

    $($strReturnValue | % { "0x$_" }) -Replace "^0x|\,$", ''
}


function Export-Function {
    
    <#
        Used to get the content of functions for building them on a remote machine.
         
        This could help to get your local functions on a remote computer, for instance
        when performing an Invoke-Command and the remote system doesn't have what you need.
         
        Example:
            [String] $code = Export-Function -Name 'Get-MyIpAddress', 'Get-GeoIpInfo'
             
            Invoke-Command -ComputerName computer1.domain.com -ScriptBlock { Invoke-Expression $Using:code; $ip = Get-MyIpAddress; Get-GeoIpInfo -IPAddress $ip }
    #>


    [CmdLetBinding()]
    Param (
        [Parameter(Mandatory, ValueFromPipeLine, HelpMessage = 'Name(s) of function to export')]
        [String[]] $Name
    )
    
    Process {
        
        foreach ($func in $Name) {
            
            $tmpFunc = Get-Command -Name $func
            
            if ($tmpFunc.CommandType -ne 'Function') {
                
                Throw 'Unable to get content of {0}. Command type of {1} -ne to function'
                return
            }
        
            'function {1} {3} {0} {2} {0} {4};{0}' -f "`r`n", $tmpFunc.Name, $tmpFunc.ScriptBlock.ToString(), '{', '}'
        }
    }
}


New-Alias -Name Add-Sig -Value Add-Signature -ErrorAction SilentlyContinue
New-Alias -Name sign -Value Add-Signature -ErrorAction SilentlyContinue
New-Alias -Name Set-EnvVar -Value Invoke-EnvironmentalVariable -ErrorAction SilentlyContinue
New-Alias -Name Get-EnvVar -Value Invoke-EnvironmentalVariable -ErrorAction SilentlyContinue
New-Alias -Name Set-EnvironmentalVariable -Value Invoke-EnvironmentalVariable -ErrorAction SilentlyContinue
New-Alias -Name Get-EnvironmentalVariable -Value Invoke-EnvironmentalVariable -ErrorAction SilentlyContinue
New-Alias -Name New-EnvVar -Value Invoke-EnvironmentalVariable -ErrorAction SilentlyContinue
New-Alias -Name Remove-EnvVar -Value Invoke-EnvironmentalVariable -ErrorAction SilentlyContinue
New-Alias -Name Remove-EnvironmentalVariable -Value Invoke-EnvironmentalVariable -ErrorAction SilentlyContinue

#endregion


#region : FILE SYSTEM FUNCTIONS


Function Invoke-Touch {
    Param
    (
        [Parameter(Mandatory = $true, Position = 1, HelpMessage = 'File path', ValueFromPipeline = $true)]
        [String]$Path,

        [Parameter(Position = 2, HelpMessage = 'Force directory')]
        [Switch] $Directory,

        [Switch]$Quiet
    )

    Begin {

    }

    Process {
        $strPath = $Path

        # See if we can figure out if asking for file or directory
        if ("$($strPath -replace '^\.')" -like '*.*' -and -not $Directory) {
            $strType = 'File'
        }

        Else {
            $strType = 'Directory'
        }

        if ((Test-Path "$strPath") -eq $true) {
            If ("$strType" -match 'File') {
                (Get-ChildItem $strPath).LastWriteTime = Get-Date
            }
        }

        Else {
            If ($Quiet) {
                $null = New-Item -Force -ItemType $strType -Path "$strPath"
            }

            Else {
                New-Item -Force -ItemType $strType -Path "$strPath"
            }
        }
    }

    End {

    }
}


Function Open-NotepadPlusPlus {
    Param
    (
        [Parameter(Mandatory = $true)]
        [Alias('Path', 'FN')]
        [String[]]$FileName
    )

    Process {
        [String] $x86 = "${env:ProgramFiles(x86)}\Notepad++\notepad++.exe"
        [String] $x64 = "${env:ProgramFiles}\Notepad++\notepad++.exe"
        
        if (Test-Path -Path $x64) {
            & $x64 $FileName
        }
        elseif (Test-Path -Path $x86) {
            & $x86 $FileName
        }
        else {
            Write-Error -Message 'It appears that you do not have Notepad++ installed on this machine'
        }
    }
}


Function New-SymLink {
    <#
            .Synopsis
            Creates symbolic links
 
            .DESCRIPTION
            This provides similar functionality to *nix ln command
 
            .EXAMPLE
            New-SymLnk -Link .\MyNewShortCut -Target '\\DataShareServer\MyShare'
 
            .EXAMPLE
            ln .\shortcut ..\FileIcantLiveWithOut.txt
 
            .NOTES
            This function requires elevation
    #>


    <#
            Version 0.2
            - Using DLL Import instead of calls to mklink.exe
    #>


    [CmdLetBinding()]
    Param
    (
        [Parameter(Position = 0)]
        [ValidateScript({ Split-Path $_ -Parent | Test-Path })]
        [String] $Link,

        [Parameter(Position = 1)]
        [ValidateScript({ Test-Path $_ })]
        [String] $Target
    )

    Begin {
        # Baseline our environment
        #Invoke-VariableBaseLine

        # Stop on error action
        $ErrorActionPreference = 'Stop'

        # Debugging for scripts
        $Script:boolDebug = $PSBoundParameters.Debug.IsPresent

        # Check if this is an elevated prompt
        [bool] $boolIsAdmin = $(Test-AdminRights)

        # Check that our DLL import exists
        Try {
            $null = [mklink.symlink]
        }

        Catch {
            Write-Error -Message '[mklink.symlink] type not loaded'
        }

        # Check if the link/file already exists.
        IF (Test-Path -Path $Link) {
            Write-Error -Message ('{0} already exists!' -f $Link)
        }
    }

    Process {

        <#
                If (Test-Path -PathType Container $Target)
                {
                $strCommand = "cmd /c mklink /d"
                }
 
                Else
                {
                $strCommand = "cmd /c mklink"
                }
                Invoke-Expression -Command ('{0} {1} {2}' -f $strCommand, $Link, $Target)
        #>


        # Variables
        $boolResult = $null

        $linkPath = Get-item -Path $(Split-Path -Path "$Link" -Parent)
        IF ($linkPath -eq $null) { $linkPath = $PWD.Path + '\' + ($Link | Split-Path -Leaf) }
        Else { $linkPath = $linkPath.FullName + '\' + $($link | Split-Path -Leaf) }

        $TargetPath = "$((Get-Item -Path $Target).FullName)"

        If (Test-Path -PathType Container $Target) {
            [int] $dwFlag = 1

            [String] $dwType = 'Directory'
        }

        Else {
            [int] $dwFlag = 0

            [String] $dwType = 'File'
        }

        Invoke-DebugIt -Console -Message 'DW Type' -Value "$dwType"

        $strCommand = '$boolResult = [mklink.symlink]::CreateSymbolicLink("{0}","{1}",{2})' -f $linkPath, $TargetPath, $dwFlag

        IF ($boolIsAdmin) {
            Invoke-Expression -Command $strCommand
        }

        Else {
            # Ask if we should elevate...
            Invoke-DebugIt -Console -Value 'This command requires elevation. Press "Y" to attempt elevation.' -Force

            $response = Read-Host -Prompt 'Continue (Y/N)?'

            IF ($response -eq 'Y') {
                $strRemoteCommand = @"
Import-Module -name Core;
 
$($strCommand);
 
IF (!`$boolResult)
{
    Invoke-DebugIt -Console -Message 'Status' -Value 'Failed to create link!' -Color 'Red' -Force
}
 
Else
{
    Invoke-DebugIt -Console -Message 'Success' -Value 'Link created successfully' -Color 'Green' -Force
}
"@

                Invoke-Elevate -Command $strRemoteCommand -Persist
            }

            Else {
                Invoke-DebugIt -Console -Value "Couldn't get it done, huh?" -Color 'Yellow' -Force
            }
        }




        IF ($boolResult = $false) {
            Invoke-DebugIt -Console -Force -Message 'Failed' -Value 'Unable to create link!' -Color 'red'
        }

        Else {
            Invoke-DebugIt -Console -Message 'Success' -Value $boolResult -Color 'Green'
        }
    }

    End {
        # Clean up the environment
        #Invoke-VariableBaseLine -Clean
    }
}


Function Remove-SymLink {
    Param
    (
        [String] $Link
    )

    If (Test-Path -PathType Leaf $Link) {
        $strCommand = "Remove-Item -Path $Link -Force"
    }

    Else {
        $dir = Get-Item -Path $Link
        $strCommand = '[System.IO.Directory]::Delete("{0}")' -f $dir
        # Making a system.io call due to junction handling in < POSH 6
    }

    Invoke-Expression -Command ('{0}' -f $strCommand)
}


Function New-HardLink {
    <#
            .Synopsis
            Creates a new hard link to a file.
 
            .DESCRIPTION
            Hard links are mappings, or system representation of a file in a single volume.
 
            .EXAMPLE
            New-HardLink -Link "$PSModulesPath\MyPoshMod\MyPoshMod.psm1" -Target 'C:\Modueles\MyPoshMod.psm1'
 
            .EXAMPLE
            New-HardLink .\testfile.txt ..\test.txt
 
            .NOTES
            You cannot make a link to a folder in any drive/volume, or link to a file in another drive/volume.
    #>


    <#
            Version 0.1
            - Day one.
    #>


    [CmdLetBinding()]
    Param
    (
        [Parameter(Mandatory = $true, Position = 0,
            HelpMessage = 'New hard link to be created')]
        [ValidateScript({ (!(Test-Path $_)) })]
        [String] $Link,

        [Parameter(Mandatory = $true, Position = 1,
            HelpMessage = 'Path to existing target file')]
        [ValidateScript({ Test-Path $_ })]
        [String] $Target
    )

    Begin {
        # Baseline our environment
        #Invoke-VariableBaseLine

        # Set the error action preference
        $ErrorActionPreference = 'Stop'

        # Debugging for scripts
        $Script:boolDebug = $PSBoundParameters.Debug.IsPresent
    }

    Process {
        # Variables
        $strTargetPath = (Get-Item -Path $Target).FullName
        $strLinkPath = Get-item -Path $(Split-Path -Path "$Link" -Parent)

        IF ($strLinkPath -eq $null) { $strLinkPath = $PWD.Path + '\' + ($Link | Split-Path -Leaf) }
        Else { $strLinkPath = $strLinkPath.FullName + '\' + $($link | Split-Path -Leaf) }

        $boolResult = [mklink.symlink]::CreateHardLink("$strLinkPath", "$strTargetPath", 0)

        IF ($boolResult) {
            Invoke-DebugIt -Console -Message 'Success' `
                -Value ('Hard link {0} created successfully' -f $strLinkPath) -Color 'Green'
        }

        Else {
            Invoke-DebugIt -Console -Force -Message 'Failed' -Value 'Unable to create hard link!' -Color 'red'
        }
    }

    End {
        # Clean up the environment
        #Invoke-VariableBaseLine -Clean
    }
}


Function Get-HardLink {
    <#
            .SYNOPSIS
            List/find files with multiple hardlinks.
 
            .DESCRIPTION
            works well to find out if one of your hardlinks, created by New-HardLink, have been broken.
 
            .PARAMETER Path
            Path to file which we would like to list hardlinks for
 
            .EXAMPLE
            Get-HardLink -Path C:\files\myFile.txt
            Returns the count and list of hardlinks and boolean value of multiple links
 
            .NOTES
            Thanks to Greg Shields for this function.
 
            .LINK
            http://www.itninja.com/blog/user/greg_shields
    #>




    [CmdLetBinding()]
    Param
    (
        [Parameter(Position = 0, Mandatory = $True, HelpMessage = "Enter a filename and path",
            ValueFromPipeline = $True)]
        $Path
    )

    Process {
        #if a file is piped in get the full file name and path
        if ($path.GetType().Name -eq "FileInfo") {
            $filepath = $path.fullname
        }
        elseif ($path.GetType().Name -eq "DirectoryInfo") {
            Write-verbose "Skipping folder $path"
            return
        }
        else {
            #otherwise assume it is a string
            $filepath = $path
        }

        #Verify path
        Write-Verbose "Testing $filepath"
        If (Test-Path $filepath) {
            $links = fsutil hardlink list $filepath
            $count = ($links | Measure-Object).Count
            If ($count -gt 1) {
                #more than one hard link found
                Write-Verbose "Found multiple links"
                $Multiple = $True
            }
            else {
                $Multiple = $False
            }

            Write-Verbose "Creating custom object"

            New-Object -TypeName PSObject -Property @{
                Path          = $filePath
                Links         = $links
                Count         = $count
                MultipleLinks = $Multiple
            }
        }
        Else {
            Write-Warning "Failed to find $filepath"
        }
    }

}


Function ConvertFrom-DosToUnix {
    <#
            .Synopsis
            Covert carriage returns to new line
 
            .DESCRIPTION
            Converts text files from DOS to Unix file new lines
 
            .EXAMPLE
            dox2unix .\test.txt
 
            .EXAMPLE
            ConvertFrom-DosToUnix -FilePath C:\Users\Me\Documents\MyFolderWithStuff\myDosFileWithText.txt
    #>


    Param
    (
        [Parameter(Mandatory = $true, HelpMessage = 'Path to file', Position = 0)]
        [ValidateScript({ Test-Path -Path $_ -PathType 'Leaf' })]
        [String] $FilePath
    )

    # get the full path to the file
    $strFilePath = $(Get-ChildItem -Path $FilePath | ForEach-Object { $_.FullName })

    # get the contents of the file to convert
    $strTempContents = [IO.File]::ReadAllText($strFilePath) -replace "`r`n", "`n"

    # set the contents of the file
    [IO.File]::WriteAllText($strFilePath, $strTempContents)
}


Function Set-DirectoryOwner {
    Param
    (
        [Parameter( Mandatory = $true )]
        [ValidateScript({
                Try {
                    $Folder = Get-Item $_ -ErrorAction Stop
                }
                Catch [System.Management.Automation.ItemNotFoundException] {
                    Throw [System.Management.Automation.ItemNotFoundException] "${_} Maybe there are network issues?"
                }
                If ($Folder.PSIsContainer) {
                    $True
                }
                Else {
                    Throw [System.Management.Automation.ValidationMetadataException] "The path '${_}' is not a container."
                }
            })] [String] $FolderPath,

        [Parameter( Mandatory = $true,
            Position = 0,
            HelpMessage = 'DOMAIN\Username',
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)]
        [ValidatePattern('.\\.')]
        [String] $UserName
    )

    $acl = Get-Acl $FolderPath.FullName

    [String] $domain = $UserName.Split('\')[0].Trim()
    [String] $uname = $UserName.Split('\')[1].Trim()

    $owner = [System.Security.Principal.NTAccount]::new($domain, $uname)

    If ($acl.Owner.ToString().Trim() -ne $UserName) {
        $acl.SetOwner($owner)

        Set-Acl -Path $FolderPath -aclobject $acl
    }
    Else {
        '{0} is already the owner of this directory'
    }
}



New-Alias -Name npp -Value Open-NotepadPlusPlus -ErrorAction SilentlyContinue
New-Alias -Name touch -Value Invoke-Touch -ErrorAction SilentlyContinue
New-Alias -Name ln -Value New-SymLink -ErrorAction SilentlyContinue
New-Alias -Name Create-SymbolicLink -Value New-SymLink -ErrorAction SilentlyContinue
New-Alias -Name dos2unix -Value ConvertFrom-DosToUnix -ErrorAction SilentlyContinue

#endregion


#region : LOG/ALERT FUNCTIONS


Function Invoke-Snitch {
    <#
            .SYNOPSIS
            Describe purpose of "Invoke-Snitch" in 1-2 sentences.
 
            .DESCRIPTION
            Add a more complete description of what the function does.
 
            .PARAMETER strMessage
            This is a required variable. Message that is sent.
 
            .EXAMPLE
            Invoke-Snitch -strMessage Value
            Describe what this call does
 
            .NOTES
            Requires that you set, somewhere in your environment: smtphost, emailto, emailfrom, and emailsubject
 
            .LINK
            URLs to related sites
            The first link is opened by Get-Help -Online Invoke-Snitch
 
            .INPUTS
            Requires a string message.
 
            .OUTPUTS
            Void.
    #>


    # Function to send an email alert to distro-list

    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]$strMessage
    )


    # Check that the required variables are set in the environment
    if ($smtphost -and $emailto -and $emailfrom -and $emailsubject -and $strMessage) {
        Send-MailMessage -SmtpServer $smtphost -To $emailto -From $emailfrom -Subject $emailsubject `
            -BodyasHTML ('{0}' -f $strMessage)

    }

    else {

        Write-Error -Message 'Not all required variables are set to invoke the snitch!'
    }
}

Function Invoke-DebugIt {
    <#
            .SYNOPSIS
            A more visually dynamic option for printing debug information.
 
            .DESCRIPTION
            Quick function to print custom debug information with complex formatting.
 
            .PARAMETER msg
            Descripter for the value to be printed. Color is gray.
 
            .PARAMETER val
            Emphasized "value" output for quick visibility when debugging. Default
            color of value is Cyan. Intentionally left as undefined variable type to
            avoid errors when presenting various types of data, possibly forgetting to
            add ToString() to the end of someting like an integer.
 
            .PARAMETER Color
            Used when you need to categorize/differentiate, visually, types of values.
            Default color is Cyan.
 
            .PARAMETER Console
            Used when you want to log to the console. Can be used when logging to file as well.
 
            .PARAMETER Logfile
            Used to log output to file. Logged as CSV
 
            .EXAMPLE
            Invoke-DebugIt -msg "Count of returned records" -val "({0} -f $($records.count)) -color Green
            Assuming that the number of records returned would be five, the following would be printed to
            the screen. Count of returned records : 5
 
            The message would be gray, and the number 5 would be Cyan, providing contrasting emphasis.
 
            .NOTES
            Pretty easy to understand. Just give it a try :)
 
    #>

    <#
            CHANGELOG:
 
            ver 0.2
            - Changed parameters to full name
            - Added aliases to the parameters so older scripts would continue to function
            - Added the ability to log to file
            - Added -Console switch parameter for specifying output type
            - Added logic for older scripts that are not console switch aware
 
            ver 0.3
            - Takes value from pipeline
            - Added positional values to parameters
            - Changed type accelerator from .NET [Boolean] to PowerShell [Bool]
            - Added application event log, logging.
 
    #>


    [CmdletBinding()]
    Param
    (
        [Parameter(
            Position = 0)]
        [Alias('msg', 'm')]
        [String] $Message,

        [Parameter(
            ValueFromPipeline = $true,
            Mandatory = $false,
            Position = 1)]
        [Alias('val', 'v')]
        $Value,

        [Alias('c')]
        [String] $Color,

        [Alias('f')]
        [Switch] $Force, # Log even if the Debug parameter is not set

        [Alias('con')]
        [Switch] $Console, # Should we log to the console

        [Switch] $EvetLog, # Add an entry to the Application Event log

        [int] $EventId = 60001, # Default event log ID

        [ValidateScript({ Test-Path -Path ($_ | Split-Path -Parent) -PathType Container })]
        [Alias('log', 'l')]
        [String] $Logfile
    )

    $ScriptVersion = '0.3'
    #[Bool] $boolDebug = $PSBoundParameters.Debug.IsPresent

    If (!($Console -and $Logfile)) {
        # Backward compatible logic
        $Console = $true
    }

    IF ($Console) {
        If ($Color) {
            $strColor = $Color
        }

        Else {
            $strColor = 'Cyan'
        }

        If ($boolDebug -or $Force) {
            Write-Host -NoNewLine -f Gray ('{0}{1} : ' -f (Get-Date).ToString('yyyyMMdd_HHmmss : '), ($Message))
            Write-Host -f $($strColor) ('{0}' -f ($Value))
        }
    }

    If ($Logfile.Length -gt 0) {
        $strSender = ('{0},{1},{2}' -f (Get-Date).ToString('yyyyMMdd_HHmmss'), $Message, $Value)
        $strSender | Out-File -FilePath $Logfile -Encoding ascii -Append
    }

    IF ($EvetLog) {
        [String] $strSource = 'PoshLogger'
        [String] $strEventLogName = 'Application'

        # Check if the source exists
        IF (!(Get-EventLog -Source $strSource -LogName $strEventLogName -Newest 1)) {
            # Check if running as Administrator
            $boolAdmin = Test-AdminRights
            IF ($boolAdmin) {
                New-EventLog -LogName $strEventLogName -Source $strSource
            }

            Else {
                Invoke-Elevate -ScriptBlock { New-EventLog -LogName $strEventLogName -Source $strSource }
            }
        }

        Write-EventLog -LogName $strEventLogName -Source $strSource -EventId $EventId -Message ($Message + $Value)
    }
}


Function Invoke-Alert {
    <#
            .Synopsis
            Audible tone that can be easily called when some event is triggered.
 
            .DESCRIPTION
            Great for monitoring things in the background, when you need to be working on something else.
 
            .PARAMETER Duration
            This is the count or duration in seconds that the tone will be generated. A value of zero will
            beep until interrupted. Negative integers will beep only once.
 
            .EXAMPLE
            The following will beep 3 times when the listed IP is reachable
            While (!(Test-Connection 8.8.8.8 -Q -C 1)) { sleep -s 1 }; Alert
 
            .EXAMPLE
            The following will beep once the IP is reachable, until you close the window, or Ctrl+C
            While (!(Test-Connection 8.8.8.8 -Q -C 1)) { sleep -s 1 }; Alert -c 0
    #>


    <#
            Version 0.1
            - Day one
    #>


    Param
    (
        [Parameter(Position = 0)]
        [Alias('Count', 'c', 'Number', 'n')]
        [Int]$Duration = 3
    )

    Process {
        # Variables
        $i = 0

        Do {
            [console]::Beep(1000, 700)
            Start-Sleep -Seconds 1

            If ($Duration -gt 0) { $i++ }
        }
        While ($i -lt $Duration)
    }
}


New-Alias -Name logger -Value Invoke-DebugIt -ErrorAction SilentlyContinue
New-Alias -Name Invoke-Logger -Value Invoke-DebugIt -ErrorAction SilentlyContinue
New-Alias -Name Alert -Value Invoke-Alert -ErrorAction SilentlyContinue

#endregion


#region : SECURITY FUNCTIONS


Function Test-AdminRights {
    ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(`
            [Security.Principal.WindowsBuiltInRole] 'Administrator')
}


Function Start-ImpersonateUser {
    Param
    (
        [Parameter(Mandatory = $true, HelpMessage = 'Scriptblock to be ran')]
        [scriptblock]$ScriptBlock,

        [Parameter(Mandatory = $true, HelpMessage = 'User to impersonate')]
        [String]$Username,

        [ValidateScript({ Test-Connection -ComputerName $_ -Quiet -Count 4 })]
        [String]$ComputerName,

        [PSCredential]$Credential
    )

    Begin {

    }

    Process {

        # Variables
        [bool] $boolHidden = $true
        [String] $strCommandExec = 'powershell'
        [String] $strCommand = "& { $ScriptBlock }"
        [String] $strEncodedCommand = [Convert]::ToBase64String($([System.Text.Encoding]::Unicode.GetBytes($strCommand)))
        [String] $strArguments = "-Nop -W Hidden -Exec ByPass -EncodedCommand $strEncodedCommand"
        [String] $strJobName = ('ImpersonationJob{0}' -f (Get-Random))
        [String] $strTempFileName = [Guid]::NewGuid().ToString('d')
        [String] $strTempFilePath = ('{0}\{1}' -f $env:TEMP, $strTempFileName)
        [String] $xml = @"
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo />
  <Triggers />
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
    <AllowHardTerminate>true</AllowHardTerminate>
    <StartWhenAvailable>false</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
    <IdleSettings />
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>$($boolHidden.ToString().ToLower())</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <WakeToRun>false</WakeToRun>
    <ExecutionTimeLimit>PT72H</ExecutionTimeLimit>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>$strCommandExec</Command>
      <Arguments>$strArguments</Arguments>
    </Exec>
  </Actions>
  <Principals>
    <Principal id="Author">
      <UserId>$Username</UserId>
      <LogonType>InteractiveToken</LogonType>
      <RunLevel>HighestAvailable</RunLevel>
    </Principal>
  </Principals>
</Task>
"@


        Try {
            $xml | Set-Content -Encoding Ascii -Path $strTempFilePath -Force
            $ErrorActionPreference = 'Stop'

            $s = New-CimSession -ComputerName $ComputerName -Credential $Credential


            $strCommandBaseCreate = 'SCHTASKS.exe /Create /TN $strJobName /XML $strTempFilePath /S $ComputerName'
            $strCommandBaseRun = 'SCHTASKS.exe /Run /TN $strJobName /S $ComputerName'
            $strCommandBaseDelete = 'SCHTASKS.exe /Delete /TN $strJobName /S $ComputerName /F'


            If ($Credential) {
                $strCommandCredential = (
                    '/U {0} /P ''{1}''' -f $Credential.UserName, $Credential.GetNetworkCredential().Password
                )

                Invoke-Expression -Command ('{0} {1}' -f $strCommandBaseCreate, $strCommandCredential)
                Invoke-Expression -Command ('{0} {1}' -f $strCommandBaseRun, $strCommandCredential)
                Invoke-Expression -Command ('{0} {1}' -f $strCommandBaseDelete, $strCommandCredential)
            }

            Else {
                Invoke-Expression -Command ('{0}' -f $strCommandBaseCreate)
                Invoke-Expression -Command ('{0}' -f $strCommandBaseRun)
                Invoke-Expression -Command ('{0}' -f $strCommandBaseDelete)
            }
        }

        Catch {
            Write-Error -Message ('Failed to run scheduled task on computer: {0}' -f $ComputerName)
        }

        Finally {
            Remove-Item -Path $strTempFilePath -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
            Remove-CimSession -CimSession $s
        }
    }

    End {

    }
}


function Get-LoggedOnUser {
    [CmdletBinding()]
    Param
    (
        [Parameter( Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('Name', 'IPAddress')]
        [String[]]$ComputerName,

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

        [Switch] $ShowEmpty
    )

    Begin {

        $wmiParams = @{
            Class       = 'Win32_Process'
            Filter      = "Name='sihost.exe' OR Name='logonUI.exe'"
        }

        if ($ErrorAction) {

            $wmiParams += @{ ErrorAction = $ErrorAction }
        }

        if ($WarningAction) {

            $wmiParams += @{ WarningAction = $WarningAction }
        }

        if ($Credential) {
            $wmiParams += @{ Credential = $Credential }
        }

        function Get-IdleInfo {

            Param (

                $processInfo
            )

            foreach ($proc in $processInfo) {

                if ($proc.Name -eq 'logonUI.exe') {

                    $id = $proc.SessionID

                    $idleTime = ([WMI] '').ConvertToDateTime($proc.CreationDate)

                    return @{
                        Id       = $id
                        IdleTime = $idleTime
                    }
                }
            }
        }
    }

    Process {
        Foreach ($Computer in $ComputerName) {
            $processinfo = @(Get-WmiObject @wmiParams -ComputerName $Computer)

            if ($processinfo.Name -contains 'sihost.exe') {

                # Need to get the idle info first, if exists
                $idleInfo = Get-IdleInfo -processInfo $processinfo

                $processinfo | Foreach-Object {

                    if ($_.Name -eq 'LogonUI.exe') {
                        return
                    }

                    $pi = $_
                    $pi.GetOwner()
                } |
                Where-Object { $_ -notcontains 'NETWORK SERVICE' -and $_ -notcontains 'LOCAL SERVICE' -and $_ -notcontains 'SYSTEM' } |
                Sort-Object -Unique -Property User |
                ForEach-Object {

                    $dt = ([WMI] '').ConvertToDateTime($pi.CreationDate)
                    $idleTime = $(

                        if ($idleInfo.Id -eq $pi.SessionId) {

                            $t = New-TimeSpan -Start $idleInfo.IdleTime -End ([datetime]::now)

                            '{0}d {1}h {2}m {3}s' -f $t.Days, $t.Hours, $t.Minutes, $t.Seconds
                        }
                        else {
                            $null
                        }
                    )

                    $ld = New-TimeSpan -Start $dt -End ([datetime]::now)
                    $logonDuration = '{0}d {1}h {2}m {3}s' -f $ld.Days, $ld.Hours, $ld.Minutes, $ld.Seconds

                    New-Object psobject -Property @{

                        Computer      = $Computer
                        Domain        = $_.Domain
                        User          = $_.User
                        SessionId     = $pi.SessionId
                        CreationDate  = $dt
                        LogonDuration = $logonDuration
                        Idle          = $(if ($idleInfo.Id -eq $pi.SessionId) { $true } else { $false })
                        IdleTime      = $idleTime
                    }
                } |
                Select-Object Computer, Domain, User, SessionId, CreationDate, LogonDuration, Idle, IdleTime
            }
            else {

                if ($ShowEmpty) {

                    New-Object psobject -Property @{

                        Computer      = $Computer
                        Domain        = $null
                        User          = $null
                        SessionId     = $null
                        CreationDate  = $null
                        LogonDuration = $null
                        Idle          = $null
                        IdleTime      = $null
                    }
                }
                else {
                    $false
                }
            }
        }
    }
}


Function Invoke-Elevate {
    <#
            TODO:
            - have output return to the main screen
            - launch the elevated process with wscript to avoid UAC
            - work out an elevated prompt, and all commands ran will use elevation until...
    #>

    [CmdLetBinding()]
    [CmdletBinding(DefaultParameterSetName = 'Command')]
    Param
    (
        # ScriptBlock: Negates the need for Command
        [Parameter(Mandatory = $false, ParameterSetName = "Command")]
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'ScriptBlock',
            HelpMessage = 'Scriptblock of commands to be executed')]
        [ScriptBlock] $ScriptBlock,

        # Command: Negates the need for ScriptBlock
        [Parameter(Mandatory = $false, ParameterSetName = 'ScriptBlock')]
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'Command',
            HelpMessage = 'Commands to be executed')]
        [String] $Command,

        [Switch] $NoProfile,

        [Switch] $Persist
    )

    Begin {
        # Invoke-VariableBaseLine

        [Bool] $boolDebug = $PSBoundParameters.Debug.IsPresent
    }

    Process {

        [String] $strCommand = "& { $ScriptBlock }"

        IF ($Command) {
            [String] $strCommand = $Command
        }

        [String] $strEncodedCommand = [Convert]::ToBase64String($([System.Text.Encoding]::Unicode.GetBytes($strCommand)))
        [String] $strArguments = "-Exec ByPass -EncodedCommand $strEncodedCommand"

        IF ($NoProfile) {
            $strArguments = + ' -Nop'
        }

        IF ($Persist) {
            $strArguments += ' -NoExit'
        }

        Start-Process PowerShell -Verb runas -ArgumentList $strArguments
    }

    End {
        # Invoke-VariableBaseLine -Clean
    }
}


Function Invoke-CredentialManager {
    <#
            .Synopsis
            Function for managing credentials for storage
 
            .DESCRIPTION
            Used to both store, and retreive a password from an XML file
 
            .EXAMPLE
            Invoke-CredentailManager -FilePath .\MySshPassord.auth
 
            Gets (if exists) or stores credentials in .\MySshPassword.auth. Will prompt for credentials if the
            file does not exist.
 
            .EXAMPLE
            Invoke-CredentailManager -FilePath .\MySshPassord.auth -Credentail $creds
 
            Stores the credentials from object "$creds" into a file in the local directory
 
            .EXAMPLE
            $strCreds = Get-Password .\test.xml
 
            PS C:\> $strCreds.Username
            Me@myDomain.local
 
            PS C:\> $strCreds.Password
            VewySecwetPassw0rd!
    #>


    <#
            Version 0.2
            - ? MACD. Move, add, change, or delete details go here. ?
            - Change : Added storing user name
            - Change : Cred file format changed to XML
            - Add : Backward compat with older script calls
    #>


    [CmdLetBinding()]
    Param
    (
        [Parameter(Mandatory = $true, Position = 0,
            HelpMessage = 'Path to where the credentials files is stored')]
        [Alias('CredentialsFile')]
        [string]$FilePath,

        [Parameter(Position = 1)]
        [PSCredential]$Credential,

        [Switch] $ReturnCredObject # Only for XML objects
    )

    Begin {
        # Baseline our environment
        #Invoke-VariableBaseLine

        # Global debugging for scripts
        $boolDebug = $PSBoundParameters.Debug.IsPresent
    }

    Process {
        # Variables
        $CredentialsFile = $FilePath



        # Check to see if the file exists
        IF (-not (Test-Path $credentialsfile)) {
            # If not, then prompt user for the credential
            IF ($Credential) {
                $creds = $Credential
            }

            Else {
                $creds = Get-Credential
            }

            $userName = $creds.UserName
            $encpassword = $creds.password

            # Create the file so we can get the full name
            Invoke-Touch -Path $CredentialsFile -Quiet

            # Must have the full path to save the XML file
            $strOutputFile = (Get-Item -Path $CredentialsFile).FullName

            [xml] $credXml = @"
<cred>
    <uname>$($userName)</uname>
    <pass>$($encpassword | ConvertFrom-SecureString)</pass>
</cred>
"@


            $credXml.Save($strOutputFile)
        }

        Else {
            # Check if we're working with XML or old
            Try {
                [xml] $xmlFile = Get-Content -Path $CredentialsFile
                $boolXml = $true
                $user = $xmlFile.cred.uname
                $encpassword = $xmlFile.cred.pass | ConvertTo-SecureString
            }

            Catch {
                $encpassword = Get-Content -Path $credentialsfile | ConvertTo-SecureString
            }

            # Use the Marshal classes to create a pointer to the secure string in memory
            $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($encpassword)

            # Change the value at the pointer back to unicode (i.e. plaintext)
            $pass = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($ptr)

            If ($boolXml) {
                If ($ReturnCredObject) {
                    $objCred = New-Object -TypeName PSCredential (
                        $user, $encpassword
                    )

                    $objCred
                }

                Else {
                    # Build and object and return it.
                    $objBuilder = New-Object -TypeName PSObject
                    $objBuilder | Add-Member -MemberType NoteProperty -Name 'Username' -Value $user
                    $objBuilder | Add-Member -MemberType NoteProperty -Name 'Password' -Value $pass

                    $objBuilder
                }
            }

            Else {
                # Return the decrypted password
                $pass
            }

        }
    }

    End {
        # Clean up the environment
        #Invoke-VariableBaseLine -Clean
        Remove-Variable -Name pass -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue

        [GC]::Collect()
    }
}


New-Alias -Name elevate -Value Invoke-Elevate -ErrorAction SilentlyContinue
New-Alias -Name sudo -Value Invoke-Elevate -ErrorAction SilentlyContinue
New-Alias -Name Store-Credentials -Value Invoke-CredentialManager -ErrorAction SilentlyContinue
New-Alias -Name Get-Password -Value Invoke-CredentialManager -ErrorAction SilentlyContinue

#endregion


#region : SYSTEM FUNCTIONS


Function Get-InstalledSoftware {
    <#
            .Synopsis
            Get installed software on the local or remote computer.
 
            .DESCRIPTION
            Uses the uninstall path to capture installed software. This is safer than using the WMI query, which
            checks the integrity upon query, and can often reconfigure, or reset application defaults. This
            function is built to scale, for quick inventory of software across your environment.
 
            .EXAMPLE
            $progs = Get-InstalledPrograms
 
            .EXAMPLE
            Get-InstalledPrograms | Select-Object -Property DisplayName, Publisher, InstallDate, Version |FT -Auto
 
            .EXAMPLE
            $swInventory = Get-InstalledSoftware -ComputerName 'cmp1','cmp2',sys3' -Credential $creds |
            Group-Object -Property PSComputerName -AsHashTable -AsString; $swInventory['cmp1']
 
            This will return and object, with all listed computer's installed software. This makes it easy to
            inventory your computers, and verify them later (if you Expot-CliXml, and Compare-Object later).
            This can scale to very large networks
    #>


    [CmdLetBinding()]
    Param
    (
        [ValidateScript({ Test-Connection -ComputerName $_ -Quiet -Count 4 }) ]
        [String[]] $ComputerName,

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

    Begin {
        # Baseline our environment
        #Invoke-VariableBaseLine

        # Debugging for scripts
        $Script:boolDebug = $PSBoundParameters.Debug.IsPresent

        # List of required modules for this function
        $arrayModulesNeeded = (
            'Core'
        )

        # Verify and load required modules
        #Test-ModuleLoaded -RequiredModules $arrayModulesNeeded -Quiet
    }

    Process {
        # Variables
        [String] $strScriptBlock = 'Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
        Invoke-DebugIt -Console -Message 'ScriptBlock' -Value $strScriptBlock

        IF ($ComputerName) {
            If ($ComputerName.Count -gt 1) {
                Invoke-DebugIt -Message '[INFO]' -Value ('Computer count: {0}' -f $ComputerName.Count)
                [String] $ComputerName = $ComputerName -join ','

                Invoke-DebugIt -Message 'Computers' -Value $ComputerName
            }
            Else {
                Invoke-DebugIt -Message '[INFO]' -Value ('Computer count: {0}' -f $ComputerName.Count)
                [String] $ComputerName = $ComputerName[0].ToString()
            }

            Invoke-DebugIt -Console -Message 'Computer name is present' -Value $ComputerName

            $strScriptBlock = '{' + $strScriptBlock + '}'
            Invoke-DebugIt -Console -Message 'Scriptblock modified' -Value $strScriptBlock

            [String] $strCommand = 'Invoke-Command -ComputerName {0} -Command {1} -Authentication Kerberos' -f $ComputerName, $strScriptBlock
            Invoke-DebugIt -Console -Message 'String command' -Value $strCommand

            IF ($Credential) {
                Invoke-DebugIt -Console -Message 'Credential is present' -Value $($Credential.UserName)

                $strCommand = $strCommand + ' -Credential $Credential'
                Invoke-DebugIt -Console -Message 'String command' -Value $strCommand
            }
        }

        Else {
            Invoke-DebugIt -Console -Value 'Local machine query' -Color 'Blue'

            $strCommand = $strScriptBlock
            Invoke-DebugIt -Console -Message 'String command' -Value $strCommand
        }

        $arrayPrograms = Invoke-Expression -Command $strCommand

        $arrayPrograms
    }

    End {
        # Clean up the environment
        #Invoke-VariableBaseLine -Clean
    }
}


Function Get-UninstallString {
    <#
            .SYNOPSIS
            Gets the uninstall string for the searched for program.
 
            .DESCRIPTION
            This function returns the entire item properties for the matched program, if found in the uninstall key.
            Output is limited.
 
            .PARAMETER ComputerName
            Name of the remote computer(s) you wish to query. Accepts from the pipeline, and will accept an array.
            If omitted, will query the local machine
 
            .PARAMETER Pattern
            Full or partial name of an installed program, on the local or remote computer.
 
            .PARAMETER Credential
            If omitted will attempt to authenticate as domain, or stored credentials
 
            .EXAMPLE
            Get-UninstallString -ComputerName wrkst01 -Pattern 'Microsoft' -Credential $myCreds
            Will return the uninstall string for all programs matching Microsoft in the DisplayName field
 
            Get-UninstallString -ComputerName wrkst01,wrkst01 -Pattern 'Microsoft' -Credential $myCreds
            Will return the uninstall string for all programs matching Microsoft in the DisplayName field, from
            both computers queried
 
            Get-UninstallString -Pattern 'Microsoft'
            Will return the uninstall string for all programs matching Microsoft on the local machine
 
            .NOTES
            N/A.
 
            .LINK
            N/A
 
            .INPUTS
            Requires a string to search for
 
            .OUTPUTS
            Returns a PSCustomeObject, or an array of PSCustomObject
    #>



    Param
    (
        [Parameter(Mandatory = $true, HelpMessage = 'name to search for')]
        [String]$Pattern,

        [Parameter(ValueFromPipeline = $true)]
        [String[]] $ComputerName,

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

    Begin {
        Function Script:Where-Match {
            Param
            (
                [Object]
                [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = 'Keys to filter')]
                $RegItem,

                [Parameter(Mandatory = $true)]
                [String] $SearchString
            )

            Process {
                if ($RegItem.DisplayName -match ('{0}' -f $SearchString)) {
                    $RegItem
                }
            }
        }

        $strRegKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
        $regCommand = 'Get-ChildItem -Path {0} | Get-ItemProperty' -f $strRegKey

        $sbRemoteKey = [ScriptBlock]::Create($regCommand)

        $Params = @{
            'ScriptBlock' = $sbRemoteKey
        }

        If ($Credential) {
            $Params.Add('Credential', $Credential)
        }

        # Too much info displayed by default... chop it down!
        [String[]] $defaultDisplaySet = @('DisplayName', 'UninstallString')

        $defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet(
            'DefaultDisplayPropertySet', [string[]]$defaultDisplaySet
        )
        $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
    }

    Process {

        If ($ComputerName) {
            $Params.Add('ComputerName', $ComputerName)
        }

        $retVal = Invoke-Command @Params | Where-Match -SearchString $Pattern

        $retVal | Add-Member -MemberType MemberSet -Name 'PSStandardMembers' -Value $PSStandardMembers

        $retVal
    }

    End {

    }
}


Function Get-USB {
    <#
            .Synopsis
            Gets USB devices attached to the system
 
            .Description
            Uses WMI to get the USB Devices attached to the system
 
            .Example
            Get-USB
 
            .Example
            Get-USB | Group-Object Manufacturer
 
            .Notes
            Thanks Lee Holmes
    #>


    Get-WmiObject -Class Win32_USBControllerDevice | Foreach-Object { [Wmi]$_.Dependent }
}


Function Add-IPRemotingTrustedHost {
    Param
    (
        [String[]] $TrustedHosts = '*',

        [Switch] $Append
    )

    $boolAdmin = Test-AdminRights

    $CurrentTrustedHosts = (Get-Item -Path WSMan:\localhost\Client\TrustedHosts).Value
    $arrayTrustedHosts = @()

    If ($Append -and $CurrentTrustedHosts -ne '') {
        $arrayTrustedHosts += $CurrentTrustedHosts
    }

    $arrayTrustedHosts += $TrustedHosts

    [String] $test = '"' + ($arrayTrustedHosts -join ',') + '"'

    [String] $strCommand = @"
 
[bool] `$boolServiceRunning = ((Get-Service -Name WinRM).Status -eq "Running");
 
If (!`$boolServiceRunning)
{
    Start-Service -Name WinRM
};
 
Set-Item WSMan:\localhost\Client\TrustedHosts -Value $test -Force;
 
"@



    [ScriptBlock] $sbCommand = [ScriptBlock]::Create($strCommand)

    If (!$boolAdmin) {
        $strTitle = 'Run as Administrator'
        $strMessage = 'This command requires administrative right. Wou you like to elevate?'
        $yes = New-Object -TypeName System.Management.Automation.Host.ChoiceDescription -ArgumentList '&Yes', `
            'Elevate and run the command'

        $no = New-Object -TypeName System.Management.Automation.Host.ChoiceDescription -ArgumentList '&No', `
            'Cancel request'

        $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)

        $result = $host.ui.PromptForChoice($strTitle, $strMessage, $options, 0)

        Switch ($result) {
            0 { Invoke-Elevate -ScriptBlock $sbCommand }
            1 { 'Exiting script' }
        }
    }
    Else {
        $sbCommand.Invoke()
    }
}


Function Get-IpRemotingTrustedHost {
    [CmdLetBinding()]
    Param
    ()

    $CurrentTrustedHosts = (Get-Item -Path WSMan:\localhost\Client\TrustedHosts).Value

    $CurrentTrustedHosts
}


Function Connect-Workstation {
    Param
    (
        [Parameter(Mandatory = $true, Position = 0)]
        [String] $ComputerName,

        [Parameter(Mandatory = $true, Position = 1)]
        [System.Management.Automation.Credential()]
        [PSCredential] $Credential,

        [ValidateSet('Default', 'Basic', 'CredSSP', 'Digest', 'Kerberos', 'Negotiate', 'NegotiateWithImplicitCredential')]
        [String] $Authentication = 'Kerberos'
    )

    $strComputerName = $ComputerName
    $UserCredential = $Credential

    Try {
        $null = [ipaddress] $ComputerName

        $objService = Get-Service -Name WinRM
        $arrayTrustedHosts = Get-IpRemotingTrustedHost -ErrorAction SilentlyContinue

        If ($objService.Status -eq 'Running' -and ($arrayTrustedHosts -contains '*' -or $arrayTrustedHosts -contains $ComputerName)) {
            Enter-PsSession -Credential $UserCredential -Authentication Default -ComputerName $strComputerName -EnableNetworkAccess
        }
        Else {
            Write-Error -Message 'WinRM is not configured properly to connect to IP addresses'
            Return
        }
    }
    Catch {
        Enter-PsSession -Credential $UserCredential -Authentication $Authentication  $strComputerName
    }
}


Function Clear-IECachedData {
    <#
            .SYNOPSIS
            Pretty easy to grasp... This function clears data cached by IE
 
            .DESCRIPTION
            Clears all, or selected cache data stored by IE
 
            .PARAMETER TempIEFiles
            Delete Temporary Internet Files
 
            .PARAMETER Cookies
            Delete Cookies
 
            .PARAMETER History
            Delete History
 
            .PARAMETER FormData
            Delete Form Data
 
            .PARAMETER Passwords
            Delete Passwords
 
            .PARAMETER All
            Delete All
 
            .PARAMETER AddOnSettings
            Delete Files and Settings Stored by Add-Ons
 
            .EXAMPLE
            Clear-IECachedData -TempIEFiles -Cookies -History -FormData -Passwords -All -AddOnSettings
            Describe what this call does
 
            .INPUTS
            N/A
 
            .OUTPUTS
            N/A
    #>




    [CmdletBinding(ConfirmImpact = 'None')]
    Param
    (
        [Parameter(HelpMessage = ' Delete Temporary Internet Files')]
        [switch] $TempIEFiles,

        [Parameter(HelpMessage = 'Delete Cookies')]
        [switch] $Cookies,

        [Parameter(HelpMessage = 'Delete History')]
        [switch] $History,

        [Parameter(HelpMessage = 'Delete Form Data')]
        [switch] $FormData,

        [Parameter(HelpMessage = 'Delete Passwords')]
        [switch] $Passwords,

        [Parameter(HelpMessage = 'Delete All')]
        [switch] $All,

        [Parameter(HelpMessage = 'Delete Files and Settings Stored by Add-Ons')]
        [switch] $AddOnSettings
    )

    if ($TempIEFiles) { & "$env:windir\system32\rundll32.exe" InetCpl.cpl, ClearMyTracksByProcess 8 }
    if ($Cookies) { & "$env:windir\system32\rundll32.exe" InetCpl.cpl, ClearMyTracksByProcess 2 }
    if ($History) { & "$env:windir\system32\rundll32.exe" InetCpl.cpl, ClearMyTracksByProcess 1 }
    if ($FormData) { & "$env:windir\system32\rundll32.exe" InetCpl.cpl, ClearMyTracksByProcess 16 }
    if ($Passwords) { & "$env:windir\system32\rundll32.exe" InetCpl.cpl, ClearMyTracksByProcess 32 }
    if ($All) { & "$env:windir\system32\rundll32.exe" InetCpl.cpl, ClearMyTracksByProcess 255 }
    if ($AddOnSettings) { & "$env:windir\system32\rundll32.exe" InetCpl.cpl, ClearMyTracksByProcess 4351 }
}


Function Get-ComObject {
    <#
            .SYNOPSIS
            Returns a list of COM objects with associated CLSID
 
            .DESCRIPTION
            This will allow you to search for full or partial CLSID. This is handy when troubleshooting DCOM
            errors from the event logs
 
            .EXAMPLE
            Get-ComObject
            Returns a list of COM objects and CLID
 
            .EXAMPLE
            Get-ComObject -CLID abcd
            Returns all COM objects that MATCH your string.
 
            .INPUTS
            String
 
            .OUTPUTS
            PSCustomObject. In the event more than one object is returned, and array of PSCustomObject
    #>


    [CmdLetBinding()]
    Param
    (
        [Parameter( Position = 0,
            HelpMessage = 'Full or partial CLSID',
            ParameterSetName = 'ScriptBlock'
        )] [Alias('ID', 'GUID')]
        [String] $CLSID,

        [Parameter( Position = 1,
            HelpMessage = 'Name of computer to manage',
            ValueFromPipeLine = $true
        )] [Alias('HostName', 'Host', 'System', 'Computer')]
        [String[]] $ComputerName,

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

    Begin {
        $Params = @{ ArgumentList = $CLSID }

        If ($ComputerName) {
            $Params += @{ ComputerName = $ComputerName }
        }

        If ($Credential) {
            $Params += @{ Credential = $Credential }
        }
    }

    Process {
        [ScriptBlock] $sb = {

            Param ( [String] $CLSID )

            $ComObjects = Get-ChildItem HKLM:\Software\Classes -ErrorAction SilentlyContinue |
            Where-Object {
                $_.PSChildName -match '^\w+\.\w+$' -and (Test-Path -Path "$($_.PSPath)\CLSID")
            } |
            Select-Object -Property `
                PSChildName, `
            @{n = 'CLSID'; e = { (Get-ItemProperty ($_.PSPath + '\clsid')).'(default)' } }

            $ComObjects | Where-Object { $_.CLSID -match $CLSID }
        }

        Invoke-Command -ScriptBlock $sb @Params
    }
}


Function Get-WindowsLicenseInfo {
    <#
            .Synopsis
            Get the license status of a Windows computer
 
            .DESCRIPTION
            Gets the license details via SLMGR.vbs /dlv
 
            .EXAMPLE
            Get-WindowsLicenseInfo
            Returns the license details of the local computer
 
            .EXAMPLE
            Get-WindowsLicenseInfo -ComputerName computer01.domain.com
            Returns the license details of the computer
    #>



    Param
    (
        [String] $ComputerName,

        [PSCredential] $Credential
    )

    Process {
        # Variables
        [ScriptBlock] $sbLicInfo = {

            ((cscript $env:windir\System32\slmgr.vbs /dlv | Select-Object -Skip 4) -replace ': ', '=') |
            ConvertFrom-StringData -ErrorAction SilentlyContinue
        }

        If ($ComputerName) {
            If ($Credential) {
                Invoke-Command -ScriptBlock $sbLicInfo -ComputerName $ComputerName -Credential $Credential `
                    -Authentication Kerberos -ErrorAction SilentlyContinue
            }
            Else {
                Invoke-Command -ScriptBlock $sbLicInfo -ComputerName $ComputerName -ErrorAction SilentlyContinue
            }
        }
        Else {
            . $sbLicInfo
        }
    }
}


Function Get-FirewallStatus {
    Param
    (
        [String]$ComputerName,

        [PSCredential]$Credential,

        [Switch]$Quiet,

        [Switch]$Debug
    )

    $boolShouldContinue = $true
    if ($Debug) { $boolDebug = $true }

    # Check if this is to be ran on a remote computer
    Try {
        If ($ComputerName) {
            $strComputerName = $ComputerName

            [ScriptBlock]$sbRemoteCommand = { (netsh adv show current) -Replace ' *', ':' }

            $strBaseCommand = "Invoke-Command -ComputerName $strComputerName -Authentication Kerberos -ScriptBlock `$sbRemoteCommand -AsJob -JobName `"remoteFwStatus`""

            # check if we're using a credential or windows auth
            If ($Credential) {
                $strRemoteCommand = $strBaseCommand + " -Credential `$Credential"

                If ($boolDebug) { Write-Host -f Red "$strRemoteCommand"; Read-Host "Press Enter to continue." };

            }
            Else {
                $strRemoteCommand = $strBaseCommand

                If ($boolDebug) { Write-Host -f Red "$strRemoteCommand"; Read-Host "Press Enter to continue." }
            }

            Invoke-Expression $strRemoteCommand | Out-Null
            Wait-Job remoteFwStatus | Out-Null # Wait on the job to finish
            $objRawOutput = Receive-Job remoteFwStatus # Receive the output from the job
            Remove-Job remoteFwStatus | Out-Null # Clean up the job that was created

        }
        Else {

            $strComputerName = $env:computername
            $objRawOutput = (netsh adv show current) -Replace ' *', ':'
        }

    }
    Catch {
        Write-Host -f Red "Something went wrong while connecting to $strComputerName"
        $boolShouldContinue = $false
    }

    If ($boolShouldContinue) {
        # Set the object for the active firewall profile
        $objActiveFirewallProfile = @()

        # Get the active policy name
        $strActiveProfile = $objRawOutput[1].Split(' ')[0].Trim()

        # loop thru to make objects... dirty, I know...
        Foreach ($field in $objRawOutput) {
            If ($field -ne '') {
                $strSplitField = $field.Split(':')

                # build the objects based on matches
                If ($strSplitField[0] -eq 'State') { $strStatusValue = $strSplitField[1] }
                If ($strSplitField[0] -eq 'LogAllowedConnections') { $strLogSuccess = $strSplitField[1] }
                If ($strSplitField[0] -eq 'LogDroppedConnections') { $strLogFailed = $strSplitField[1] }
                If ($strSplitField[0] -eq 'FileName') { $strFilePath = $strSplitField[1] }
                If ($strSplitField[0] -eq 'MaxFileSize') { [int]$intFileSize = $strSplitField[1] }
            }
        }

        # build an object to return
        $item = New-Object PSObject

        $item | Add-Member -type NoteProperty -Name 'ComputerName' -Value "$strComputerName"
        $item | Add-Member -type NoteProperty -Name 'ActiveProfile' -Value "$strActiveProfile"
        $item | Add-Member -type NoteProperty -Name 'State' -Value "$strStatusValue"
        $item | Add-Member -type NoteProperty -Name 'LogAllowedConnections' -Value "$strLogSuccess"
        $item | Add-Member -type NoteProperty -Name 'LogDroppedConnections' -Value "$strLogFailed"
        $item | Add-Member -type NoteProperty -Name 'MaxFileSize' -Value $intFileSize
        $item | Add-Member -type NoteProperty -Name 'FileName' -Value "$strFilePath"

        # Put all tha things in tha thing...
        $objActiveFirewallProfile = $item

        If ($Quiet) {
            If ($objActiveFirewallProfile.State -eq 'ON') {
                $true
            }
            Else {
                $false
            }
        }
        Else {
            $objActiveFirewallProfile
        }
    }
}


#endregion


#region UNDER_CONSTRUCTION


<#
        Function Private:Dev_Invoke-Elevate
        {
 
        # TODO:
        # - have output return to the main screen
        # - launch the elevated process with wscript to avoid UAC
        # - work out an elevated prompt, and all commands ran will use elevation until...
 
        [CmdLetBinding()]
        [CmdletBinding(DefaultParameterSetName='Command')]
        Param
        (
        # ScriptBlock: Negates the need for Command
        [Parameter(Mandatory=$false,ParameterSetName="Command")]
        [Parameter(Mandatory=$true, Position=0,ParameterSetName='ScriptBlock',
        HelpMessage='Scriptblock of commands to be executed')]
        [ScriptBlock] $ScriptBlock,
 
        # Command: Negates the need for ScriptBlock
        [Parameter(Mandatory=$false, ParameterSetName='ScriptBlock')]
        [Parameter(Mandatory=$true, Position=0, ParameterSetName='Command',
        HelpMessage='Commands to be executed')]
        [String] $Command,
 
        [Switch] $NoProfile,
 
        [Switch] $Persist
        )
 
        Begin
        {
        # Invoke-VariableBaseLine
 
        [Bool] $boolDebug = $PSBoundParameters.Debug.IsPresent
        }
 
        Process
        {
 
        [String] $strCommand = "& { $ScriptBlock }"
 
        IF ($Command)
        {
        [String] $strCommand = $Command
        }
 
        [String] $strEncodedCommand = [Convert]::ToBase64String($([System.Text.Encoding]::Unicode.GetBytes($strCommand)))
        [String] $strArguments = "-Exec ByPass -EncodedCommand $strEncodedCommand"
 
        IF ($NoProfile)
        {
        $strArguments =+ ' -Nop'
        }
 
        IF ($Persist)
        {
        $strArguments += ' -NoExit'
        }
 
        Start-Process PowerShell -Verb runas -ArgumentList $strArguments
        }
 
        End
        {
        # Invoke-VariableBaseLine -Clean
        }
        }
#>



#endregion