PSTwitter.psm1

#=======================================================================#
#
# Author: Collin Chaffin
# Last Modified: 10/28/2014 12:15 AM
# Filename: PSTwitter.psm1
#
#
# Changelog:
#
# v 1.0.0.1 : 10/18/2014 : Initial release
# v 1.0.0.2 : 10/28/2014 : Added 7 new special char to pre(hex) escape
# and altered code to deal with powershell
# throwing ex for already valid param length
# due to escaping adding % char length
#
# Notes:
#
# This module utilizes personal Twitter's user-specific API
# information to perform OAuth connection to Twitter and submit either a
# Tweet or a direct message to a single Twitter recipient. This module
# was inspired by Adam Bertram and others but became a rewrite to more formal modular
# functions using objects which perhaps can spur additional development
# interest and community-contributed growth and hopefully simplify required
# code changes to address Twitter API changes in the future!
#
#
# Installation Instructions:
#
# Run the MSI installer or, if installing manually, copy the
# PSTwitter.psm1 and PSTwitter.psd files to:
# "%PSModulePath%PSTwitter"
#
# HINT: To manually create the module folder prior to copying:
# mkdir "%PSModulePath%PSTwitter"
#
# Once installed/copied, open Windows Powershell and execute:
# Import-Module PSTwitter
#
# Store your Twitter API information by executing:
# Set-TwitterOAuthTokens
#
# If you have gotten this far, you should be able to send your
# first Tweet by executing:
# Send-TwitterTweet -TweetMessage "Testing the #PSTwitter #Powershell Module!"
#
# Verification:
#
# Check "%PSModulePath%PSTwitter\Logs" folder for a daily rotating log.
# Example log for successful Tweet:
#
# 10/18/2014 21:48:57 :: [INFO] :: START - Set-TwitterOAuthTokens function execution
# 10/18/2014 21:48:58 :: [INFO] :: FINISH - Set-TwitterOAuthTokens function execution
# 10/18/2014 21:51:07 :: [INFO] :: START - Send-TwitterTweet function execution
# 10/18/2014 21:51:07 :: [INFO] :: START - ConvertTo-HexEscaped function execution
# 10/18/2014 21:51:07 :: [INFO] :: FINISH - ConvertTo-HexEscaped function execution
# 10/18/2014 21:51:07 :: [INFO] :: START - Connect-OAuthTwitter function execution
# 10/18/2014 21:51:07 :: [INFO] :: START - Loading DOTNET assemblies
# 10/18/2014 21:51:07 :: [INFO] :: FINISH - Loading DOTNET assemblies
# 10/18/2014 21:51:07 :: [INFO] :: START - Retrieving Twitter API settings from registry
# 10/18/2014 21:51:07 :: [INFO] :: FINISH - Retrieving Twitter API settings from registry
# 10/18/2014 21:51:07 :: [INFO] :: START - New-TwitterOAuthNonce function execution
# 10/18/2014 21:51:07 :: [INFO] :: START - Generating oAuthNonce string
# 10/18/2014 21:51:07 :: [INFO] :: FINISH - Generating oAuthNonce string
# 10/18/2014 21:51:07 :: [INFO] :: FINISH - New-TwitterOAuthNonce function execution
# 10/18/2014 21:51:07 :: [INFO] :: START - New-TwitterOAuthTimeStamp function execution
# 10/18/2014 21:51:07 :: [INFO] :: FINISH - New-TwitterOAuthTimeStamp function execution
# 10/18/2014 21:51:07 :: [INFO] :: START - New-TwitterOAuthSignature function execution
# 10/18/2014 21:51:07 :: [INFO] :: START - Building OAuth signature
# 10/18/2014 21:51:07 :: [INFO] :: FINISH - Building OAuth signature
# 10/18/2014 21:51:07 :: [INFO] :: FINISH - New-TwitterOAuthSignature function execution
# 10/18/2014 21:51:07 :: [INFO] :: START - New-TwitterOAuthString function execution
# 10/18/2014 21:51:07 :: [INFO] :: FINISH - New-TwitterOAuthString function execution
# 10/18/2014 21:51:07 :: [INFO] :: FINISH - Connect-OAuthTwitter function execution
# 10/18/2014 21:51:07 :: [INFO] :: START - Sending HTTP POST via REST to Twitter
# 10/18/2014 21:51:08 :: [INFO] :: FINISH - Sending HTTP POST via REST to Twitter
# 10/18/2014 21:51:08 :: [INFO] :: FINISH - Send-TwitterTweet function execution
#
#=======================================================================#


#region Globals

#########################################################################
# Global Variables #
#########################################################################

# General Variables
# Disable psTwitterDebugging for zero output and logging
$psTwitterDebugging = $true
$psTwitterLogging = $true

# Twitter-specific API variables that may change in the future
$Global:psTwitterEndpointTweet            = 'https://api.twitter.com/1.1/statuses/update.json'
$Global:psTwitterEndpointDirectMessage    = 'https://api.twitter.com/1.1/direct_messages/new.json'
$Global:psTwitterOAuthSignatureMethod    = 'HMAC-SHA1'
$Global:psTwitterOAuthVersion            = '1.0'

# Paths
$Global:psTwitterInvocationPath = $([System.IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) + "\")
$Global:psTwitterLogPath = $($psTwitterInvocationPath) + "Logs\"
# Override this with a static manual path, if so desired or it defaults to \Logs folder in Module location

#########################################################################

#endregion

#region Functions

#########################################################################
# Functions #
#########################################################################

function Connect-OAuthTwitter
{
    <#
    .SYNOPSIS
        This function utilizes personal Twitter's user-specific API information to perform
        OAuth connection to Twitter and set up the final OAuth string needed to then post a
        Tweet or a direct message to a single Twitter recipient using the REST API.
         
    .DESCRIPTION
        Author: Collin Chaffin
        Description: This function utilizes personal Twitter's user-specific API
                        information to perform OAuth connection to Twitter and submit either a
                        Tweet or a direct message to a single Twitter recipient.
     
    .PARAMETER TweetMessage
        Tweet message text
     
    .PARAMETER DirectMessage
        Direct message text
     
    .PARAMETER To
        Single Twitter recipient to whom you are sending a direct message
     
    .EXAMPLE
        This example generates all required OAuth information and sets up final OAuth string to
        then send a direct message using the REST API:
            $oAuthRequestString = (Connect-OAuthTwitter -DirectMessage "The #PSTwitter Powershell Module is working!" -To "CollinChaffin")
            Invoke-RestMethod .....-Headers @{ 'Authorization' = $oAuthRequestString }.....
     
    .EXAMPLE
        This example generates all required OAuth information and sets up final OAuth string to
        then send a tweet using the REST API:
            $oAuthRequestString = (Connect-OAuthTwitter -TweetMessage "Testing the #PSTwitter Windows Powershell module for Twitter!")
            Invoke-RestMethod .....-Headers @{ 'Authorization' = $oAuthRequestString }.....
    #>

    [CmdletBinding(DefaultParameterSetName = 'Tweeting')]
    [OutputType([System.String])]
    param    (        
        [Parameter(ParameterSetName = 'Tweeting', Mandatory = $true, HelpMessage = 'Please enter tweet message text')]
            [ValidateNotNullOrEmpty()]
            [System.String]
            $TweetMessage,
        [Parameter(ParameterSetName = 'Direct',
            Mandatory = $true,
            HelpMessage = 'Please enter your direct message text and note the TO switch is also required for target recipient')]
            [ValidateNotNullOrEmpty()]
            [System.String]
            $DirectMessage,
        [Parameter(ParameterSetName = 'Direct',
            Mandatory = $true,
            HelpMessage = 'Please enter Twitter recipient to whom you are sending a direct message')]
            [ValidateNotNullOrEmpty()]
            [System.String]
            $To
    )    
    BEGIN
    {        
        (Write-Status -Message "START - Connect-OAuthTwitter function execution" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
        try
        {
            (Write-Status -Message "START - Loading DOTNET assemblies" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
            [Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null
            [Reflection.Assembly]::LoadWithPartialName("System.Net") | Out-Null
            [Reflection.Assembly]::LoadWithPartialName("System.Web") | Out-Null
            (Write-Status -Message "FINISH - Loading DOTNET assemblies" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
        }
        catch
        {
            Throw $("ERROR OCCURRED WHILE LOADING REQUIRED DOTNET ASSEMBLIES " + $_.Exception.Message)            
        }
        
        # Retrieve required user-specific Twitter API info from registry
        try
        {            
            if ($((Test-Path -Path HKCU:\Software\PSTwitter) -eq $false) `
               -or $((Get-Item HKCU:\Software\PSTwitter).getvalue("APIKey") -eq $null) `
               -or $((Get-Item HKCU:\Software\PSTwitter).getvalue("APISecret") -eq $null) `
               -or $((Get-Item HKCU:\Software\PSTwitter).getvalue("AccessToken") -eq $null) `
               -or $((Get-Item HKCU:\Software\PSTwitter).getvalue("AccessTokenSecret") -eq $null)
               )
            {
                (Write-Status -Message "Twitter API settings not found - prompting operator" -Status "WARNING" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
                # Call Set-TwitterOAuthTokens function to prompt for credentials and store them
                Set-TwitterOAuthTokens
            }
            else
            {
                (Write-Status -Message "START - Retrieving Twitter API settings from registry" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
                $global:apiKey                = (Get-Item HKCU:\Software\PSTwitter).getvalue("APIKey")
                $global:apiSecret            = (Get-Item HKCU:\Software\PSTwitter).getvalue("APISecret")
                $global:accessToken            = (Get-Item HKCU:\Software\PSTwitter).getvalue("AccessToken")
                $global:accessTokenSecret    = (Get-Item HKCU:\Software\PSTwitter).getvalue("AccessTokenSecret")
                (Write-Status -Message "FINISH - Retrieving Twitter API settings from registry" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
            }            
        }
        catch
        {
            Throw $("ERROR OCCURRED WHILE LOADING REQUIRED TWITTER API INFORMATION " + $_.Exception.Message)
        }
    }
    PROCESS
    {
        try
        {
            # Create a custom PSObject to store all require oAuth info and simply pass a single object to helper functions
            $objOAuth = @()
            $objOAuth = New-Object -TypeName PSObject
            $objOAuth | Add-Member -Name 'oauth_consumer_key' -Value $($apiKey) -MemberType NoteProperty -Force
            $objOAuth | Add-Member -Name 'oauth_signature_method' -Value $($psTwitterOAuthSignatureMethod) -MemberType NoteProperty -Force
            $objOAuth | Add-Member -Name 'oauth_token' -Value $($accessToken) -MemberType NoteProperty -Force
            $objOAuth | Add-Member -Name 'oauth_version' -Value $($psTwitterOAuthVersion) -MemberType NoteProperty -Force
            $objOAuth | Add-Member -Name 'oauth_urlAPIendpoint' -Value $(switch ($PsCmdlet.ParameterSetName) { "Tweeting"{ $psTwitterEndpointTweet }; "Direct"{ $psTwitterEndpointDirectMessage }; }) -MemberType NoteProperty -Force
            $objOAuth | Add-Member -Name 'oauth_consumer_key_secret' -Value $($APISecret) -MemberType NoteProperty -Force
            $objOAuth | Add-Member -Name 'oauth_token_secret' -Value $($accessTokenSecret) -MemberType NoteProperty -Force
            
            # Generate Nonce key PSObject property using helper function
            $objOAuth | Add-Member -Name 'oauth_nonce' -Value $(New-TwitterOAuthNonce) -MemberType NoteProperty -Force
            
            # Generate OAuth epoch-based timestamp PSObject property using helper function
            $objOAuth | Add-Member -Name 'oauth_timestamp' -Value $(New-TwitterOAuthTimeStamp) -MemberType NoteProperty -Force
            
            # Determine are we tweeting or sending a direct message on our parameter set call
            switch ($PSCmdlet.ParameterSetName)
            {
                'Tweeting'
                {
                    # Since we are tweeting, add the tweet message to the custom PSObject as property required for signature
                    $objOAuth | Add-Member -Name 'oauth_tweetmessage' -Value $($TweetMessage) -MemberType NoteProperty -Force
                    
                    # Generate OAuth signature for tweet request
                    $oAuthSignature = (New-TwitterOAuthSignature -objOAuth $objOAuth -Tweeting)
                }
                'Direct'
                {
                    # Since we are sending a direct message, add the message text and recipient to the custom PSObject as property required for signature
                    $objOAuth | Add-Member -Name 'oauth_directmessage' -Value $($DirectMessage) -MemberType NoteProperty -Force
                    $objOAuth | Add-Member -Name 'oauth_directrecipient' -Value $($To) -MemberType NoteProperty -Force
                    
                    # Generate OAuth signature for direct message request
                    $oAuthSignature = (New-TwitterOAuthSignature -objOAuth $objOAuth -Direct)
                }
            }
            
            # Add the generated final signature as a property to the same custom PSObject
            $objOAuth | Add-Member -Name 'oauth_signature' -Value $($oAuthSignature) -MemberType NoteProperty -Force
            
            # Finally, generate the final OAuth request string with all the above generated information passing one single custom PSObject
            [string]$oAuthRequestString = (New-TwitterOAuthString -objOAuth $objOAuth)
            
            # Return the one single oAuth request POST string to hand back to calling function (tweet or direct message) to POST it
            Return $oAuthRequestString;
        }
        catch
        {
            Throw $("ERROR OCCURRED WHILE BUILDING OAUTH REQUEST " + $_.Exception.Message)
        }
    }
    END
    {
        (Write-Status -Message "FINISH - Connect-OAuthTwitter function execution" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
    }
}


function New-TwitterOAuthNonce
{
    <#
    .SYNOPSIS
        Generate a new Nonce key for Twitter oAuth
 
    .DESCRIPTION
        Author: Collin Chaffin
        Description: This function generates a new Nonce key for Twitter oAuth
         
    .EXAMPLE
        $Nonce = New-TwitterOAuthNonce
         
        $Nonce
        s70FjIUXCOXeSX063Oop1ysZfCvlKQvJ9u1gqrVMuCU1
    #>

    [CmdletBinding()]
    [OutputType([System.String])]
    param (
    )
    BEGIN
    {
        (Write-Status -Message "START - New-TwitterOAuthNonce function execution" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
    }
    PROCESS
    {
        try
        {
            (Write-Status -Message "START - Generating oAuthNonce string" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
            
            # Create RNGCryptoServiceProvider object and call methods to generate oAuthNonce
            $barrayKey = New-Object byte[](32)
            $cryptoGen = New-Object -TypeName System.Security.Cryptography.RNGCryptoServiceProvider
            $cryptoGen.GetBytes($barrayKey)            
            [string]$oAuthNonce = [System.Web.HttpServerUtility]::UrlTokenEncode($barrayKey)
            
            (Write-Status -Message "FINISH - Generating oAuthNonce string" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
            
            return $oAuthNonce;
        }
        catch
        {
            Throw $("ERROR OCCURRED GENERATING NEW NONCE KEY " + $_.Exception.Message)
        }
        finally
        {
            # Dispose of RNGCryptoServiceProvider object
            $cryptoGen.Dispose()
        }
    }
    END
    {
        (Write-Status -Message "FINISH - New-TwitterOAuthNonce function execution" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
    }
}

function New-TwitterOAuthTimeStamp
{
    <#
    .SYNOPSIS
        Generate a new OAuth timestamp for Twitter oAuth based on epoch time
 
    .DESCRIPTION
        Author: Collin Chaffin
        Description: This function generates a new OAuth timestamp for
                        Twitter oAuth based on epoch time
         
    .EXAMPLE
        $oAuthTimeStamp = New-TwitterOAuthTimeStamp
         
        $oAuthTimeStamp
        1412487014
    #>

    [CmdletBinding()]
    [OutputType([System.String])]
    param (
    )
    BEGIN
    {
        (Write-Status -Message "START - New-TwitterOAuthTimeStamp function execution" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
    }
    PROCESS
    {
        try
        {
            [string]$oAuthTimeStamp = [INT](New-TimeSpan "01 January 1970 00:00:00" $((Get-Date).ToUniversalTime())).TotalSeconds            
            return $oAuthTimeStamp;
        }
        catch
        {
            Throw $("ERROR OCCURRED GENERATING NEW OAUTH EPOCH TIMESTAMP KEY " + $_.Exception.Message)
        }
    }
    END
    {
        (Write-Status -Message "FINISH - New-TwitterOAuthTimeStamp function execution" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
    }
}

function New-TwitterOAuthSignature
{
    <#
    .SYNOPSIS
        Generate a new signature for Twitter oAuth request
 
    .DESCRIPTION
        Author: Collin Chaffin
        Description: This function generates a new signature for Twitter oAuth request
         
    .PARAMETER objOAuth
        [PSObject] Custom PSObject containing all required OAuth information
     
    .EXAMPLE
        $oAuthSignature = (New-TwitterOAuthSignature -objOAuth $objOAuth -Tweeting)
     
        $oAuthSignature
        ptUHUftvP0l6JQoJ+7yBa//uZcE=
    #>

    [CmdletBinding(DefaultParameterSetName = 'Tweeting')]
    [OutputType([System.String])]
    param (
        [Parameter(Position = 0, ParameterSetName = 'Tweeting', Mandatory = $true)]
        [Parameter(Position = 0, ParameterSetName = 'Direct', Mandatory = $true)]
            [ValidateNotNullOrEmpty()]
            [PSObject]
            $objOAuth,
        [Parameter(ParameterSetName = 'Tweeting',Mandatory = $false)]        
            [Switch]
            $Tweeting,
        [Parameter(ParameterSetName = 'Direct', Mandatory = $false)]        
            [Switch]
            $Direct    
    )
    BEGIN
    {
        (Write-Status -Message "START - New-TwitterOAuthSignature function execution" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
    }
    PROCESS
    {
        try
        {
            (Write-Status -Message "START - Building OAuth signature" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
            
            # Build base signature authorization parameters
            $oAuthSignatureBase = 'POST&'
            $oAuthSignatureBase += [System.Uri]::EscapeDataString("$($objOAuth.PSObject.Properties["oauth_urlAPIendpoint"].Value)")+"&"            
            $oAuthSignatureBase += [System.Uri]::EscapeDataString("$($objOAuth.PSObject.Properties["oauth_consumer_key"].Name)=$($objOAuth.PSObject.Properties["oauth_consumer_key"].Value)&")
            $oAuthSignatureBase += [System.Uri]::EscapeDataString("$($objOAuth.PSObject.Properties["oauth_nonce"].Name)=$($objOAuth.PSObject.Properties["oauth_nonce"].Value)&")
            $oAuthSignatureBase += [System.Uri]::EscapeDataString("$($objOAuth.PSObject.Properties["oauth_signature_method"].Name)=$($objOAuth.PSObject.Properties["oauth_signature_method"].Value)&")
            $oAuthSignatureBase += [System.Uri]::EscapeDataString("$($objOAuth.PSObject.Properties["oauth_timestamp"].Name)=$($objOAuth.PSObject.Properties["oauth_timestamp"].Value)&")
            $oAuthSignatureBase += [System.Uri]::EscapeDataString("$($objOAuth.PSObject.Properties["oauth_token"].Name)=$($objOAuth.PSObject.Properties["oauth_token"].Value)&")
            $oAuthSignatureBase += [System.Uri]::EscapeDataString("$($objOAuth.PSObject.Properties["oauth_version"].Name)=$($objOAuth.PSObject.Properties["oauth_version"].Value)&")
            
            # Add final values to signature authorization parameters depending on whether it is a Tweet or direct message
            switch ($PSCmdlet.ParameterSetName)
            {
                'Tweeting'
                {
                    $oAuthSignatureBase += [System.Uri]::EscapeDataString("status=$($objOAuth.PSObject.Properties["oauth_tweetmessage"].Value)")
                }
                'Direct'
                {
                    $oAuthSignatureBase += [System.Uri]::EscapeDataString("screen_name=$($objOAuth.PSObject.Properties["oauth_directrecipient"].Value)&")
                    $oAuthSignatureBase += [System.Uri]::EscapeDataString("text=$($objOAuth.PSObject.Properties["oauth_directmessage"].Value)")                    
                }
            }
            $oAuthSignatureBase = $oAuthSignatureBase | sort
            
            # Create a SHA1 hash from the oAuth signature using apisecret+accesstokensecret as HMACSHA1 key
            $signatureKey = [System.Uri]::EscapeDataString($($objOAuth.PSObject.Properties["oauth_consumer_key_secret"].Value)) + "&" + [System.Uri]::EscapeDataString($($objOAuth.PSObject.Properties["oauth_token_secret"].Value))
            
            # Create HMACSHA1 object and call method using key to create hash
            $objSHA1 = New-Object -TypeName System.Security.Cryptography.HMACSHA1
            $objSHA1.Key = [System.Text.Encoding]::ASCII.GetBytes($signatureKey)
            $oAuthSignature = [System.Convert]::ToBase64String($objSHA1.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($oAuthSignatureBase)));
            
            (Write-Status -Message "FINISH - Building OAuth signature" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
            
            return $oAuthSignature;
        }
        catch
        {
            Throw $("ERROR OCCURRED GENERATING NEW OAUTH SIGNATURE " + $_.Exception.Message)
        }
        finally
        {
            # Dispose of HMACSHA1 object
            $objSHA1.Dispose()
        }
    }
    END
    {
        (Write-Status -Message "FINISH - New-TwitterOAuthSignature function execution" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
    }
}


function New-TwitterOAuthString
{
    <#
    .SYNOPSIS
        Generate a new string for Twitter oAuth request
 
    .DESCRIPTION
        Author: Collin Chaffin
        Description: This function generates a new string for Twitter oAuth request
         
    .PARAMETER objOAuth
        [PSObject] Custom PSObject containing all required OAuth information
         
    .EXAMPLE
        $oAuthRequestString = (New-TwitterOAuthString -objOAuth $objOAuth)
    #>

    [CmdletBinding()]
    [OutputType([System.String])]
    param (
        [Parameter(Position = 0, Mandatory = $true)]
            [ValidateNotNullOrEmpty()]
            [PSObject]
            $objOAuth        
    )
    BEGIN
    {
        (Write-Status -Message "START - New-TwitterOAuthString function execution" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
    }
    PROCESS
    {
        try
        {
            # Build full oAuth string now that the object is complete and this string unlike signature does NOT include tweet message, etc.
            $oAuthRequestString = 'OAuth '
            $oAuthRequestString += (($objOAuth.PSObject.Properties["oauth_consumer_key"].Name) + '="' + [System.Uri]::EscapeDataString($objOAuth.PSObject.Properties["oauth_consumer_key"].Value) + '", ')
            $oAuthRequestString += (($objOAuth.PSObject.Properties["oauth_nonce"].Name) + '="' + [System.Uri]::EscapeDataString($objOAuth.PSObject.Properties["oauth_nonce"].Value) + '", ')
            $oAuthRequestString += (($objOAuth.PSObject.Properties["oauth_signature"].Name) + '="' + [System.Uri]::EscapeDataString($objOAuth.PSObject.Properties["oauth_signature"].Value) + '", ')
            $oAuthRequestString += (($objOAuth.PSObject.Properties["oauth_signature_method"].Name) + '="' + [System.Uri]::EscapeDataString($objOAuth.PSObject.Properties["oauth_signature_method"].Value) + '", ')
            $oAuthRequestString += (($objOAuth.PSObject.Properties["oauth_timestamp"].Name) + '="' + [System.Uri]::EscapeDataString($objOAuth.PSObject.Properties["oauth_timestamp"].Value) + '", ')
            $oAuthRequestString += (($objOAuth.PSObject.Properties["oauth_token"].Name) + '="' + [System.Uri]::EscapeDataString($objOAuth.PSObject.Properties["oauth_token"].Value) + '", ')
            $oAuthRequestString += (($objOAuth.PSObject.Properties["oauth_version"].Name) + '="' + [System.Uri]::EscapeDataString($objOAuth.PSObject.Properties["oauth_version"].Value) + '"')
            
            return $oAuthRequestString;            
        }
        catch
        {
            Throw $("ERROR OCCURRED GENERATING NEW OAUTH REQUEST STRING " + $_.Exception.Message)
        }
    }
    END
    {
        (Write-Status -Message "FINISH - New-TwitterOAuthString function execution" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
    }
}


function Write-Status
{
    <#
    .SYNOPSIS
        Write a status message to console and log if debugging
 
    .DESCRIPTION
        Author: Collin Chaffin
        Description: This function writes a status message out to console
                        appending exact time/date of command execution and will
                        optionally write to daily log
 
    .PARAMETER Message
        [String] Message to write
             
    .PARAMETER Status
        [String] Status code string
     
    .PARAMETER Debugging
        [Bool] If this switch is true then output debugging to console
     
    .EXAMPLE
        Write-Status -Message "Public Tweet sent successfully" -Status "SUCCESS" -Debugging $debugging
     
        10/18/2014 21:00:00 :: [SUCCESS] :: Public Tweet sent successfully
    #>

    [CmdletBinding()]
    [OutputType([System.String])]
    param (
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]        
            [System.String]
            $Message,        
        [Parameter(Mandatory = $false)]
            [System.String]
            $Status = "INFO",
        [Parameter(Mandatory = $false)]
            [Switch]
            $Debugging,
        [Parameter(Mandatory = $false)]
            [Switch]
            $Logging,
        [Parameter(Mandatory = $false)]
            [System.String]
            $LogPath = $(($psTwitterInvocationPath) + "Logs\")
    )
    BEGIN
    {
        try
        {    
            # Do not do anything unless global script debugging is true
            If ($Debugging -eq $true)
            {
                # Set up variables and log file/path
                [String]$statusTime = (Get-Date -Format "MM/dd/yyyy HH:mm:ss")
                
                # If -Logging passed, set up logging a a DAILY log file (change path in globals at top of script)
                if ($Logging -eq $true)
                {
                    If (!(Test-Path $psTwitterLogPath)) { New-Item -ItemType Directory -Force -Path ($psTwitterLogPath) | Out-Null }
                    [String]$logFileDate = (Get-Date -Format "MM-dd-yyyy")
                    [String]$logFile = $($psTwitterLogPath) + "PSTwitter-" + $logFileDate + ".log"
                }
            }                
        }
        catch
        {
            Throw $("ERROR OCCURRED WHILE WRITING OUTPUT " + $_.Exception.Message)
        }
    }
    PROCESS
    {
        try
        {
            # Do not do anything unless global script debugging is true
            If ($Debugging -eq $true)
            {
                # Ensure custom status is always uppercase
                $Status = $Status.ToUpper()
                
                # Format output message
                $Message = "$statusTime :: [$Status] :: $Message"
                
                # Write out to console
                Write-Host $Message -ForegroundColor Cyan
                    
                # If -Logging passed, set up logging a a DAILY log file (change path in globals at top of script)
                if ($Logging -eq $true)
                {                
                    Add-Content -Path $logFile -Value ($Message)
                }
            }
        }
        catch
        {
            Throw $("ERROR OCCURRED WRITING STATUS" + $_.Exception.Message)
        }
    }
    END
    {
    }
}

Function Set-TwitterOAuthTokens
{
  <#
    .SYNOPSIS
        Stores required Twitter API OAuth settings providing both GUI wizard and
        command-line options
         
 
    .DESCRIPTION
        Author: Collin Chaffin
        Description: This function stores the required Twitter API settings
                        provided by the operator interactively into the HKCU
                        registry hive for subsequent sessions providing both
                        GUI wizard and command-line options
 
    .PARAMETER Force
        [Switch] Clear existing stored Twitter API information and repopulate
 
    .PARAMETER APIKey
        [String] Twitter API Key
 
    .PARAMETER APISecret
        [String] Twitter API Secret
 
    .PARAMETER AccessToken
        [String] Twitter Access Token
 
    .PARAMETER AccessTokenSecret
        [String] Twitter Access Token Secret
 
    .EXAMPLE
        Set-TwitterOAuthTokens
 
        If Twitter API settings are not found in the registry, prompt the operator
        interactively via a GUI wizard to provide and open the Twitter API webpage
        to assist operator in locating their user-specific Twitter application information
         
        NOTE: Only missing information will be requested via wizard interface
 
    .EXAMPLE
        Set-TwitterOAuthTokens -Force
 
        Remove existing Twitter API information from registry and repopulate via
        GUI wizard
 
    .EXAMPLE
        Set-TwitterOAuthTokens -Force -APIKey "01234567890"
 
        Remove existing Twitter API information from registry and repopulate via
        automatically detected "command-line" mode. In this case because all
        four required pieces of information were not provided, the missing three
        will be interactively prompted but via standard commandline text prompting
    #>

    [CmdletBinding(DefaultParameterSetName = 'Wizard')]
    [OutputType([System.String])]
    param
    (
        [Parameter(ParameterSetName = 'CmdLine', Mandatory = $false)]
        [Parameter(ParameterSetName = 'Wizard', Mandatory = $false)]
            [Switch]
            $Force,
        [Parameter(ParameterSetName = 'CmdLine', Mandatory = $false, HelpMessage = 'Please enter your personal Twitter APPLICATION API Key:')]
            [ValidateNotNullOrEmpty()]
            [System.String]
            $APIKey,
        [Parameter(ParameterSetName = 'CmdLine', Mandatory = $false, HelpMessage = 'Please enter your personal Twitter APPLICATION API Secret:')]
            [ValidateNotNullOrEmpty()]
            [System.String]
            $APISecret,
        [Parameter(ParameterSetName = 'CmdLine', Mandatory = $false, HelpMessage = 'Please enter your personal Twitter APPLICATION Access Token:')]
            [ValidateNotNullOrEmpty()]
            [System.String]
            $AccessToken,
        [Parameter(ParameterSetName = 'CmdLine', Mandatory = $false, HelpMessage = 'Please enter your personal Twitter APPLICATION Access Token Secret:')]
            [ValidateNotNullOrEmpty()]
            [System.String]
            $AccessTokenSecret
        
    )
    BEGIN
    {
        (Write-Status -Message "START - Set-TwitterOAuthTokens function execution" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
    }
    PROCESS
    {
        # If we were passed -Force switch
        if ($Force.IsPresent)
        {
            try
            {                
                    # Force switch used, remove/clear all stored API info and drop to either wizard or cmdline to repopulate
                    if ($((Get-Item HKCU:\Software\PSTwitter).getvalue("APIKey") -ne $null)) { Remove-ItemProperty HKCU:\Software\PSTwitter -name "APIKey" }
                    if ($((Get-Item HKCU:\Software\PSTwitter).getvalue("APISecret") -ne $null)) { Remove-ItemProperty HKCU:\Software\PSTwitter -name "APISecret" }
                    if ($((Get-Item HKCU:\Software\PSTwitter).getvalue("AccessToken") -ne $null)) { Remove-ItemProperty HKCU:\Software\PSTwitter -name "AccessToken" }
                    if ($((Get-Item HKCU:\Software\PSTwitter).getvalue("AccessTokenSecret") -ne $null)) { Remove-ItemProperty HKCU:\Software\PSTwitter -name "AccessTokenSecret" }                
            }
            catch
            {
                Throw $("ERROR OCCURRED CLEARING TWITTER API INFORMATION FROM REGISTRY " + $_.Exception.Message)
            }
        }
        
        # (Re)Populate the registry with 4 pieces of required Twitter OAuth info
        try
        {    
            # If any single piece of info is missing, start the process
            if ($((Test-Path -Path HKCU:\Software\PSTwitter) -eq $false) `
               -or $((Get-Item HKCU:\Software\PSTwitter).getvalue("APIKey") -eq $null) `
               -or $((Get-Item HKCU:\Software\PSTwitter).getvalue("APISecret") -eq $null) `
               -or $((Get-Item HKCU:\Software\PSTwitter).getvalue("AccessToken") -eq $null) `
               -or $((Get-Item HKCU:\Software\PSTwitter).getvalue("AccessTokenSecret") -eq $null)
               )
            {
                Write-Host "`nPlease configure your personal Twitter application from which you must store the following pieces of information:`n`n""API key""`n""API secret""`n""Access Token""`n""Access Token Secret""`n`nOpening default browser to: https://apps.twitter.com" -ForegroundColor Yellow
                
                Start-Process "https://apps.twitter.com/"
                
                # Entire reg key is missing so create it
                if (!(Test-Path -Path HKCU:\Software\PSTwitter))
                {
                    (Write-Status -Message "START - PSTwitter registry key creation" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
                    New-Item -Path HKCU:\Software -Name PSTwitter | out-null
                    (Write-Status -Message "FINISH - PSTwitter registry key creation" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
                }
                
                Switch ($PSCmdlet.ParameterSetName)
                {
                    'Wizard'
                    {
                        # Now that we are sure reg key exists, call the wizard form and prompt only for missing value(s)
                        # NOTE: If reg key exists and only 2 pieces of info are missing, operator only receives a wizard with 2 pages with 4 being all info missing
                        Call-PSTwitter-API_psf | Out-Null
                    }
                    'CmdLine'
                    {
                        if (!$APIKey)
                        {
                            Write-Host "`n`nEnter Twitter API Key:" -ForegroundColor Yellow -NoNewline
                            $APIKey = Read-Host                            
                            if ($APIKey) { New-ItemProperty HKCU:\Software\PSTwitter -name "APIKey" -value "$APIKey" | out-null }
                        }
                        else
                        {
                            New-ItemProperty HKCU:\Software\PSTwitter -name "APIKey" -value "$APIKey" | out-null
                        }
                        if (! $APISecret)
                        {
                            Write-Host "Enter Twitter API Secret:" -ForegroundColor Yellow -NoNewline
                            $APISecret = Read-Host
                            if ($APISecret) { New-ItemProperty HKCU:\Software\PSTwitter -name "APISecret" -value "$APISecret" | out-null }
                        }
                        else
                        {
                            New-ItemProperty HKCU:\Software\PSTwitter -name "APISecret" -value "$APISecret" | out-null
                        }
                        if (! $AccessToken)
                        {
                            Write-Host "Enter Twitter Access Token:" -ForegroundColor Yellow -NoNewline
                            $AccessToken = Read-Host
                            if ($AccessToken) { New-ItemProperty HKCU:\Software\PSTwitter -name "AccessToken" -value "$AccessToken" | out-null }
                        }
                        else
                        {
                            New-ItemProperty HKCU:\Software\PSTwitter -name "AccessToken" -value "$AccessToken" | out-null
                        }
                        if (! $AccessTokenSecret)
                        {
                            Write-Host "Enter Twitter Access Token Secret:" -ForegroundColor Yellow -NoNewline
                            $AccessTokenSecret = Read-Host
                            if ($AccessTokenSecret) { New-ItemProperty HKCU:\Software\PSTwitter -name "AccessTokenSecret" -value "$AccessTokenSecret" | out-null }
                            Write-Host "`n"
                        }
                        else
                        {
                            New-ItemProperty HKCU:\Software\PSTwitter -name "AccessTokenSecret" -value "$AccessTokenSecret" | out-null
                        }
                    }
                }
            }
        }
        catch
        {
            Throw $("ERROR OCCURRED WRITING TWITTER API INFORMATION TO REGISTRY " + $_.Exception.Message)
        }
        finally
        {
            # Now that the reg values are present regardless of method, read back in the values and set our globals
            $global:apiKey = (Get-Item HKCU:\Software\PSTwitter).getvalue("APIKey")
            $global:apiSecret = (Get-Item HKCU:\Software\PSTwitter).getvalue("APISecret")
            $global:accessToken = (Get-Item HKCU:\Software\PSTwitter).getvalue("AccessToken")
            $global:accessTokenSecret = (Get-Item HKCU:\Software\PSTwitter).getvalue("AccessTokenSecret")
        }
    }
    END
    {
        (Write-Status -Message "FINISH - Set-TwitterOAuthTokens function execution" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
    }
    
}

function ConvertTo-HexEscaped
{
    <#
    .SYNOPSIS
        Hex escapes specific set of special characters that Twitter API does
        not handle properly
 
    .DESCRIPTION
        Author: Collin Chaffin
        Description: Hex escapes specific set of special characters that Twitter API does
                        not handle properly
 
    .PARAMETER InputText
        [String] Twitter text to hex escape
     
    .EXAMPLE
        ConvertTo-HexEscaped -InputText "Testing!"
        Testing%21
    #>

    [CmdletBinding()]
    [OutputType([System.String])]
    param (
        [Parameter(Mandatory = $true)]        
        [System.String]
        $InputText
    )
    BEGIN
    {
        (Write-Status -Message "START - ConvertTo-HexEscaped function execution" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
    }
    PROCESS
    {
        try
        {            
            # Handle the special characters Twitter does not handle properly and escape them
            [string[]] $specialChar = @('%', '=', "+", "&", '[', ']', "!", "*", "'", "(", ")", ",")
            
            for ($i = 0; $i -lt $specialChar.Length; $i++)
            {
                $InputText = $InputText.Replace($specialChar[$i], [System.Uri]::HexEscape($specialChar[$i]))                
            }            
            return $InputText
        }
        catch
        {
            Throw $("ERROR OCCURRED CONVERTING SPECIAL CHARACTERS " + $_.Exception.Message)
        }
    }
    END
    {
        (Write-Status -Message "FINISH - ConvertTo-HexEscaped function execution" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
    }
}


function Send-TwitterTweet
{
    <#
    .SYNOPSIS
        Sends a Twitter Tweet
     
    .DESCRIPTION
        Author: Collin Chaffin
        Description: Sends a Twitter Tweet using OAuth and REST
     
    .PARAMETER TweetMessage
        The message text of the tweet to be posted
     
    .EXAMPLE
        Send-TwitterTweet -TweetMessage "This is my first tweet using the #PSTwitter Powershell Module!"
     
        This example will send a tweet with the above tweet text
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateLength(1, 140)]
        [System.String]
        $TweetMessage
    )
    BEGIN
    {
        (Write-Status -Message "START - Send-TwitterTweet function execution" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
    }
    PROCESS
    {
        try
        {            
            # Twitter does not handle a few special characters properly so let's hexescape them
            $fixedTweetMessage = $(ConvertTo-HexEscaped -InputText $TweetMessage)
            
            # Call our main connect routine to do the oAuth heavy lifting
            $oAuthRequestString = (Connect-OAuthTwitter -TweetMessage $fixedTweetMessage)
            
            # Set up the HTTP POST body
            $httpBody = "status=$fixedTweetMessage"
            
            (Write-Status -Message "START - Sending HTTP POST via REST to Twitter" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
            
            # Call the REST API to handle the final OAUTH POST
            Invoke-RestMethod -URI $psTwitterEndpointTweet -Method Post -Body $httpBody -Headers @{ 'Authorization' = $oAuthRequestString } -ContentType "application/x-www-form-urlencoded" | Out-Null
            
            (Write-Status -Message "FINISH - Sending HTTP POST via REST to Twitter" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
        }
        catch
        {
            Throw $("ERROR OCCURRED SENDING TWEET " + $_.Exception.Message)
        }
    }
    END
    {
        (Write-Status -Message "FINISH - Send-TwitterTweet function execution" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
    }
}

function Send-TwitterDirect
{
    <#
    .SYNOPSIS
        Sends a Twitter direct message to another single Twitter user by screen name
     
    .DESCRIPTION
            Author: Collin Chaffin
            Description: Sends a Twitter direct message to another single Twitter user by screen name
                            using OAuth and REST
     
    .PARAMETER DirectMessage
        Text of the direct message
     
    .PARAMETER To
        Single recipient screen name
     
    .EXAMPLE
        Send-TwitterDirect -Message "The #PSTwitter Powershell Module is working!" -To "CollinChaffin"
                 
    .NOTES
        There is a maximim limit of 250 direct messages in a 24 hour period!
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateLength(1, 140)]
        [System.String]
        $DirectMessage,
        [Parameter(Mandatory = $true)]
        [System.String]
        $To
    )
    
    BEGIN
    {
        (Write-Status -Message "START - Send-TwitterDirect function execution" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
    }
    PROCESS
    {
        try
        {
            # Twitter does not handle a few special characters properly so let's hexescape them
            $fixedDirectMessage = $(ConvertTo-HexEscaped -InputText $DirectMessage)
            
            # Call our main connect routine to do the oAuth heavy lifting
            $oAuthRequestString = (Connect-OAuthTwitter -DirectMessage $fixedDirectMessage -To $To)
            
            # Should not have to do this but run recipient though standard escaping just in case
            $To = [System.Uri]::EscapeDataString($To)
            
            # Set up the HTTP POST body
            $httpBody = "text=$fixedDirectMessage&screen_name=$To"
            
            (Write-Status -Message "START - Sending HTTP POST via REST to Twitter" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
            
            # Call the REST API to handle the final OAUTH POST
            Invoke-RestMethod -URI $psTwitterEndpointDirectMessage -Method Post -Body $httpBody -Headers @{ 'Authorization' = $oAuthRequestString } -ContentType "application/x-www-form-urlencoded" | Out-Null
            
            (Write-Status -Message "FINISH - Sending HTTP POST via REST to Twitter" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
        }
        catch
        {
            Throw $("ERROR OCCURRED SENDING DIRECT MESSAGE " + $_.Exception.Message)
        }
    }
    END
    {
        (Write-Status -Message "FINISH - Send-TwitterDirect function execution" -Status "INFO" -Debugging:$psTwitterDebugging -Logging:$psTwitterLogging -Logpath $psTwitterLogPath)
    }
}

#########################################################################

#endregion

#region Call-PSTwitter-API_psf

function Call-PSTwitter-API_psf
{
    #----------------------------------------------
    #region Import the Assemblies
    #----------------------------------------------
    [void][reflection.assembly]::Load('mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
    [void][reflection.assembly]::Load('System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
    [void][reflection.assembly]::Load('System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
    [void][reflection.assembly]::Load('System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
    [void][reflection.assembly]::Load('System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a')
    [void][reflection.assembly]::Load('System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
    [void][reflection.assembly]::Load('System.DirectoryServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a')
    [void][reflection.assembly]::Load('System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
    [void][reflection.assembly]::Load('System.ServiceProcess, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a')
    #endregion Import Assemblies
    
    #----------------------------------------------
    #region Form Objects
    #----------------------------------------------
    [System.Windows.Forms.Application]::EnableVisualStyles()
    $frmTwitterAPIInformation = New-Object 'System.Windows.Forms.Form'
    $buttonCancel = New-Object 'System.Windows.Forms.Button'
    $buttonBack = New-Object 'System.Windows.Forms.Button'
    $buttonFinish = New-Object 'System.Windows.Forms.Button'
    $tabcontrolWizard = New-Object 'System.Windows.Forms.TabControl'
    $tabpageStep1 = New-Object 'System.Windows.Forms.TabPage'
    $txtAPIKey = New-Object 'System.Windows.Forms.TextBox'
    $labelAPIKey = New-Object 'System.Windows.Forms.Label'
    $tabpageStep2 = New-Object 'System.Windows.Forms.TabPage'
    $txtAPISecret = New-Object 'System.Windows.Forms.TextBox'
    $labelAPISecret = New-Object 'System.Windows.Forms.Label'
    $tabpageStep3 = New-Object 'System.Windows.Forms.TabPage'
    $txtAccessToken = New-Object 'System.Windows.Forms.TextBox'
    $labelAccessToken = New-Object 'System.Windows.Forms.Label'
    $tabpageStep4 = New-Object 'System.Windows.Forms.TabPage'
    $txtAccessTokenSecret = New-Object 'System.Windows.Forms.TextBox'
    $labelAccessTokenSecret = New-Object 'System.Windows.Forms.Label'
    $buttonNext = New-Object 'System.Windows.Forms.Button'
    $InitialFormWindowState = New-Object 'System.Windows.Forms.FormWindowState'
    #endregion Form Objects
    
    
    function Validate-WizardPage
    {
        [OutputType([boolean])]
        param ([System.Windows.Forms.TabPage]$tabPage)
        
        if ($tabPage -eq $tabpageStep1)
        {
            if (-not $txtAPIKey.Text)
            {
                return $false
            }
            
            return $true
        }
        elseif ($tabPage -eq $tabpageStep2)
        {
            if (-not $txtAPISecret.Text)
            {
                return $false
            }
            
            return $true
        }
        elseif ($tabPage -eq $tabpageStep3)
        {
            if (-not $txtAccessToken.Text)
            {
                return $false
            }
            
            return $true
        }
        elseif ($tabPage -eq $tabpageStep4)
        {
            if (-not $txtAccessTokenSecret.Text)
            {
                return $false
            }
            
            return $true
        }
        return $false
    }
    
    $buttonFinish_Click = {
        if ($txtAPIKey.Text) { New-ItemProperty HKCU:\Software\PSTwitter -name "APIKey" -value "$($txtAPIKey.Text)" | out-null }
        if ($txtAPISecret.Text) { New-ItemProperty HKCU:\Software\PSTwitter -name "APISecret" -value "$($txtAPISecret.Text)" | out-null }
        if ($txtAccessToken.Text) { New-ItemProperty HKCU:\Software\PSTwitter -name "AccessToken" -value "$($txtAccessToken.Text)" | out-null }
        if ($txtAccessTokenSecret.Text) { New-ItemProperty HKCU:\Software\PSTwitter -name "AccessTokenSecret" -value "$($txtAccessTokenSecret.Text)" | out-null }
    }
    
    #region Events and Functions
    $frmTwitterAPIInformation_Load = {
        Update-NavButtons
        
        # Reg key is there, but we must have a missing value(s)
        $apiKey = (Get-Item HKCU:\Software\PSTwitter).getvalue("APIKey")
        $apiSecret = (Get-Item HKCU:\Software\PSTwitter).getvalue("APISecret")
        $accessToken = (Get-Item HKCU:\Software\PSTwitter).getvalue("AccessToken")
        $accessTokenSecret = (Get-Item HKCU:\Software\PSTwitter).getvalue("AccessTokenSecret")
        
        # Check for any single missing values and prompt for those that are missing
        if ($apiKey)
        {
            $tabcontrolWizard.TabPages.Remove($tabpageStep1)
        }
        if ($apiSecret)
        {
            $tabcontrolWizard.TabPages.Remove($tabpageStep2)
        }
        if ($accessToken)
        {
            $tabcontrolWizard.TabPages.Remove($tabpageStep3)
        }
        if ($accessTokenSecret)
        {
            $tabcontrolWizard.TabPages.Remove($tabpageStep4)
        }
    }
    
    function Update-NavButtons
    {
        <#
            .DESCRIPTION
            Validates the current tab and Updates the Next, Prev and Finish buttons.
        #>

        $enabled = Validate-WizardPage $tabcontrolWizard.SelectedTab
        $buttonNext.Enabled = $enabled -and ($tabcontrolWizard.SelectedIndex -lt $tabcontrolWizard.TabCount - 1)
        $buttonBack.Enabled = $tabcontrolWizard.SelectedIndex -gt 0
        $buttonFinish.Enabled = $enabled -and ($tabcontrolWizard.SelectedIndex -eq $tabcontrolWizard.TabCount - 1)
        #Uncomment to Hide Buttons
        #$buttonNext.Visible = ($tabcontrolWizard.SelectedIndex -lt $tabcontrolWizard.TabCount - 1)
        #$buttonFinish.Visible = ($tabcontrolWizard.SelectedIndex -eq $tabcontrolWizard.TabCount - 1)
    }
    
    $script:DeselectedIndex = -1
    $tabcontrolWizard_Deselecting = [System.Windows.Forms.TabControlCancelEventHandler]{
        #Event Argument: $_ = [System.Windows.Forms.TabControlCancelEventArgs]
        # Store the previous tab index
        $script:DeselectedIndex = $_.TabPageIndex
    }
    
    $tabcontrolWizard_Selecting = [System.Windows.Forms.TabControlCancelEventHandler]{
        #Event Argument: $_ = [System.Windows.Forms.TabControlCancelEventArgs]
        # We only validate if we are moving to the Next TabPage.
        # Users can move back without validating
        if ($script:DeselectedIndex -ne -1 -and $script:DeselectedIndex -lt $_.TabPageIndex)
        {
            #Validate each page until we reach the one we want
            for ($index = $script:DeselectedIndex; $index -lt $_.TabPageIndex; $index++)
            {
                $_.Cancel = -not (Validate-WizardPage $tabcontrolWizard.TabPages[$index])
                
                if ($_.Cancel)
                {
                    # Cancel and Return if validation failed.
                    return;
                }
            }
        }
        Update-NavButtons
    }
    
    $buttonBack_Click = {
        #Go to the previous tab page
        if ($tabcontrolWizard.SelectedIndex -gt 0)
        {
            $tabcontrolWizard.SelectedIndex--
        }
    }
    
    $buttonNext_Click = {
        #Go to the next tab page
        if ($tabcontrolWizard.SelectedIndex -lt $tabcontrolWizard.TabCount - 1)
        {
            $tabcontrolWizard.SelectedIndex++
        }
    }
    
    #endregion
    
    #------------------------------------------------------
    # Events: Call Update-NavButtons to trigger validation
    #------------------------------------------------------
    
    $txtAPIKey_TextChanged = {
        Update-NavButtons
    }
    
    $txtAPISecret_TextChanged = {
        Update-NavButtons
    }
    
    $txtAccessToken_TextChanged = {
        Update-NavButtons
    }
    
    $txtAccessTokenSecret_TextChanged = {
        Update-NavButtons
    }
    
    
    $tabcontrolWizard_SelectedIndexChanged = {
        Update-NavButtons
    }
    
    $buttonCancel_Click = {
        $frmTwitterAPIInformation.Close()
    }
    
    #----------------------------------------------
    #region cleanup Events
    #----------------------------------------------
    
    $Form_StateCorrection_Load =
    {
        #Correct the initial state of the form to prevent the .Net maximized form issue
        $frmTwitterAPIInformation.WindowState = $InitialFormWindowState
    }
    
    $Form_StoreValues_Closing =
    {
        #Store the control values
        $script:PSTwitter_API_txtAPIKey = $txtAPIKey.Text
        $script:PSTwitter_API_txtAPISecret = $txtAPISecret.Text
        $script:PSTwitter_API_txtAccessToken = $txtAccessToken.Text
        $script:PSTwitter_API_txtAccessTokenSecret = $txtAccessTokenSecret.Text
    }
    
    
    $Form_Cleanup_FormClosed =
    {
        #Remove all event handlers from the controls
        try
        {
            $buttonCancel.remove_Click($buttonCancel_Click)
            $buttonBack.remove_Click($buttonBack_Click)
            $buttonFinish.remove_Click($buttonFinish_Click)
            $txtAPIKey.remove_TextChanged($txtAPIKey_TextChanged)
            $txtAPISecret.remove_TextChanged($txtAPISecret_TextChanged)
            $txtAccessToken.remove_TextChanged($txtAccessToken_TextChanged)
            $txtAccessTokenSecret.remove_TextChanged($txtAccessTokenSecret_TextChanged)
            $tabcontrolWizard.remove_SelectedIndexChanged($tabcontrolWizard_SelectedIndexChanged)
            $tabcontrolWizard.remove_Selecting($tabcontrolWizard_Selecting)
            $tabcontrolWizard.remove_Deselecting($tabcontrolWizard_Deselecting)
            $buttonNext.remove_Click($buttonNext_Click)
            $frmTwitterAPIInformation.remove_Load($frmTwitterAPIInformation_Load)
            $frmTwitterAPIInformation.remove_Load($Form_StateCorrection_Load)
            $frmTwitterAPIInformation.remove_Closing($Form_StoreValues_Closing)
            $frmTwitterAPIInformation.remove_FormClosed($Form_Cleanup_FormClosed)
        }
        catch [Exception]
        { }
    }
    #endregion cleanup Events
    
    #----------------------------------------------
    #region Form Code
    #----------------------------------------------
    $frmTwitterAPIInformation.SuspendLayout()
    $tabcontrolWizard.SuspendLayout()
    $tabpageStep1.SuspendLayout()
    $tabpageStep2.SuspendLayout()
    $tabpageStep3.SuspendLayout()
    $tabpageStep4.SuspendLayout()
    #
    # frmTwitterAPIInformation
    #
    $frmTwitterAPIInformation.Controls.Add($buttonCancel)
    $frmTwitterAPIInformation.Controls.Add($buttonBack)
    $frmTwitterAPIInformation.Controls.Add($buttonFinish)
    $frmTwitterAPIInformation.Controls.Add($tabcontrolWizard)
    $frmTwitterAPIInformation.Controls.Add($buttonNext)
    $frmTwitterAPIInformation.AcceptButton = $buttonFinish
    $frmTwitterAPIInformation.CancelButton = $buttonCancel
    $frmTwitterAPIInformation.ClientSize = '537, 180'
    $frmTwitterAPIInformation.FormBorderStyle = 'FixedDialog'
    $frmTwitterAPIInformation.MaximizeBox = $False
    $frmTwitterAPIInformation.Name = "frmTwitterAPIInformation"
    $frmTwitterAPIInformation.StartPosition = 'CenterScreen'
    $frmTwitterAPIInformation.Text = "Twitter API Information"
    $frmTwitterAPIInformation.add_Load($frmTwitterAPIInformation_Load)
    #
    # buttonCancel
    #
    $buttonCancel.Anchor = 'Bottom, Right'
    $buttonCancel.DialogResult = 'Cancel'
    $buttonCancel.Location = '369, 145'
    $buttonCancel.Name = "buttonCancel"
    $buttonCancel.Size = '75, 23'
    $buttonCancel.TabIndex = 4
    $buttonCancel.Text = "&Cancel"
    $buttonCancel.UseVisualStyleBackColor = $True
    $buttonCancel.add_Click($buttonCancel_Click)
    #
    # buttonBack
    #
    $buttonBack.Anchor = 'Bottom, Left'
    $buttonBack.Location = '13, 145'
    $buttonBack.Name = "buttonBack"
    $buttonBack.Size = '75, 23'
    $buttonBack.TabIndex = 1
    $buttonBack.Text = "< &Back"
    $buttonBack.UseVisualStyleBackColor = $True
    $buttonBack.add_Click($buttonBack_Click)
    #
    # buttonFinish
    #
    $buttonFinish.Anchor = 'Bottom, Right'
    $buttonFinish.DialogResult = 'OK'
    $buttonFinish.Location = '450, 145'
    $buttonFinish.Name = "buttonFinish"
    $buttonFinish.Size = '75, 23'
    $buttonFinish.TabIndex = 3
    $buttonFinish.Text = "&Finish"
    $buttonFinish.UseVisualStyleBackColor = $True
    $buttonFinish.add_Click($buttonFinish_Click)
    #
    # tabcontrolWizard
    #
    $tabcontrolWizard.Controls.Add($tabpageStep1)
    $tabcontrolWizard.Controls.Add($tabpageStep2)
    $tabcontrolWizard.Controls.Add($tabpageStep3)
    $tabcontrolWizard.Controls.Add($tabpageStep4)
    $tabcontrolWizard.Anchor = 'Top, Bottom, Left, Right'
    $tabcontrolWizard.Location = '13, 12'
    $tabcontrolWizard.Name = "tabcontrolWizard"
    $tabcontrolWizard.SelectedIndex = 0
    $tabcontrolWizard.Size = '512, 127'
    $tabcontrolWizard.TabIndex = 0
    $tabcontrolWizard.add_SelectedIndexChanged($tabcontrolWizard_SelectedIndexChanged)
    $tabcontrolWizard.add_Selecting($tabcontrolWizard_Selecting)
    $tabcontrolWizard.add_Deselecting($tabcontrolWizard_Deselecting)
    #
    # tabpageStep1
    #
    $tabpageStep1.Controls.Add($txtAPIKey)
    $tabpageStep1.Controls.Add($labelAPIKey)
    $tabpageStep1.Location = '4, 22'
    $tabpageStep1.Name = "tabpageStep1"
    $tabpageStep1.Padding = '3, 3, 3, 3'
    $tabpageStep1.Size = '504, 101'
    $tabpageStep1.TabIndex = 0
    $tabpageStep1.Text = "API Key"
    $tabpageStep1.UseVisualStyleBackColor = $True
    #
    # txtAPIKey
    #
    $txtAPIKey.Location = '168, 43'
    $txtAPIKey.Name = "txtAPIKey"
    $txtAPIKey.Size = '259, 20'
    $txtAPIKey.TabIndex = 1
    $txtAPIKey.add_TextChanged($txtAPIKey_TextChanged)
    #
    # labelAPIKey
    #
    $labelAPIKey.AutoSize = $True
    $labelAPIKey.Location = '87, 46'
    $labelAPIKey.Name = "labelAPIKey"
    $labelAPIKey.Size = '45, 13'
    $labelAPIKey.TabIndex = 0
    $labelAPIKey.Text = "API Key"
    #
    # tabpageStep2
    #
    $tabpageStep2.Controls.Add($txtAPISecret)
    $tabpageStep2.Controls.Add($labelAPISecret)
    $tabpageStep2.Location = '4, 22'
    $tabpageStep2.Name = "tabpageStep2"
    $tabpageStep2.Padding = '3, 3, 3, 3'
    $tabpageStep2.Size = '504, 101'
    $tabpageStep2.TabIndex = 1
    $tabpageStep2.Text = "API Secret"
    $tabpageStep2.UseVisualStyleBackColor = $True
    #
    # txtAPISecret
    #
    $txtAPISecret.Location = '168, 42'
    $txtAPISecret.Name = "txtAPISecret"
    $txtAPISecret.Size = '259, 20'
    $txtAPISecret.TabIndex = 3
    $txtAPISecret.add_TextChanged($txtAPISecret_TextChanged)
    #
    # labelAPISecret
    #
    $labelAPISecret.AutoSize = $True
    $labelAPISecret.Location = '87, 45'
    $labelAPISecret.Name = "labelAPISecret"
    $labelAPISecret.Size = '58, 13'
    $labelAPISecret.TabIndex = 2
    $labelAPISecret.Text = "API Secret"
    #
    # tabpageStep3
    #
    $tabpageStep3.Controls.Add($txtAccessToken)
    $tabpageStep3.Controls.Add($labelAccessToken)
    $tabpageStep3.Location = '4, 22'
    $tabpageStep3.Name = "tabpageStep3"
    $tabpageStep3.Size = '504, 101'
    $tabpageStep3.TabIndex = 2
    $tabpageStep3.Text = "Access Token"
    $tabpageStep3.UseVisualStyleBackColor = $True
    #
    # txtAccessToken
    #
    $txtAccessToken.Location = '168, 43'
    $txtAccessToken.Name = "txtAccessToken"
    $txtAccessToken.Size = '259, 20'
    $txtAccessToken.TabIndex = 5
    $txtAccessToken.add_TextChanged($txtAccessToken_TextChanged)
    #
    # labelAccessToken
    #
    $labelAccessToken.AutoSize = $True
    $labelAccessToken.Location = '87, 46'
    $labelAccessToken.Name = "labelAccessToken"
    $labelAccessToken.Size = '76, 13'
    $labelAccessToken.TabIndex = 4
    $labelAccessToken.Text = "Access Token"
    #
    # tabpageStep4
    #
    $tabpageStep4.Controls.Add($txtAccessTokenSecret)
    $tabpageStep4.Controls.Add($labelAccessTokenSecret)
    $tabpageStep4.Location = '4, 22'
    $tabpageStep4.Name = "tabpageStep4"
    $tabpageStep4.Padding = '3, 3, 3, 3'
    $tabpageStep4.Size = '504, 101'
    $tabpageStep4.TabIndex = 3
    $tabpageStep4.Text = "Access Token Secret"
    $tabpageStep4.UseVisualStyleBackColor = $True
    #
    # txtAccessTokenSecret
    #
    $txtAccessTokenSecret.Location = '168, 44'
    $txtAccessTokenSecret.Name = "txtAccessTokenSecret"
    $txtAccessTokenSecret.Size = '259, 20'
    $txtAccessTokenSecret.TabIndex = 7
    $txtAccessTokenSecret.add_TextChanged($txtAccessTokenSecret_TextChanged)
    #
    # labelAccessTokenSecret
    #
    $labelAccessTokenSecret.AutoSize = $True
    $labelAccessTokenSecret.Location = '52, 46'
    $labelAccessTokenSecret.Name = "labelAccessTokenSecret"
    $labelAccessTokenSecret.Size = '110, 13'
    $labelAccessTokenSecret.TabIndex = 6
    $labelAccessTokenSecret.Text = "Access Token Secret"
    #
    # buttonNext
    #
    $buttonNext.Anchor = 'Bottom, Right'
    $buttonNext.Location = '288, 145'
    $buttonNext.Name = "buttonNext"
    $buttonNext.Size = '75, 23'
    $buttonNext.TabIndex = 2
    $buttonNext.Text = "&Next >"
    $buttonNext.UseVisualStyleBackColor = $True
    $buttonNext.add_Click($buttonNext_Click)
    $tabpageStep4.ResumeLayout()
    $tabpageStep3.ResumeLayout()
    $tabpageStep2.ResumeLayout()
    $tabpageStep1.ResumeLayout()
    $tabcontrolWizard.ResumeLayout()
    $frmTwitterAPIInformation.ResumeLayout()
    #endregion Form Code
    
    #----------------------------------------------
    
    #Save the initial state of the form
    $InitialFormWindowState = $frmTwitterAPIInformation.WindowState
    #Init the OnLoad event to correct the initial state of the form
    $frmTwitterAPIInformation.add_Load($Form_StateCorrection_Load)
    #Clean up the control events
    $frmTwitterAPIInformation.add_FormClosed($Form_Cleanup_FormClosed)
    #Store the control values when form is closing
    $frmTwitterAPIInformation.add_Closing($Form_StoreValues_Closing)
    #Show the Form
    return $frmTwitterAPIInformation.ShowDialog()
}

#endregion

Export-ModuleMember Send-TwitterTweet
Export-ModuleMember Send-TwitterDirect
Export-ModuleMember Set-TwitterOAuthTokens