Invoke-ConnectExchange.psm1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
Function Invoke-ConnectExchange {
    <#
        .SYNOPSIS
            Simple function to connect into Exchange

        .DESCRIPTION
            This function will allow you to connect with Exchange using PowerShell.

        .NOTES
            Author : Thomas ILLIET, contact@thomas-illiet.fr
            Date : 2017-08-01
            Last Update : 2017-08-01
            Test Date : 2017-10-17
            Version : 2.1.0
        
        .PARAMETER config
            Configuration Array

        .EXAMPLE
            #----------------------------------------------
            # With Plain Password
            #----------------------------------------------
            $ExchangeConfig =@{
                Identity = "unicorn@microsoft.fr"
                Password = "BeatifullUnicorne!"
                Authentication = "Basic"
                ConnectionUri = "https://outlook.office365.com/powershell-liveid/"
                Cmdlet = @('Set-Mailbox')
                SessionName = "Exchange"
            }
            Connect-Exchange -Config $ExchangeConfig

        .EXAMPLE
            #----------------------------------------------
            # With SecureString file
            #----------------------------------------------
            $ExchangeConfig =@{
                Identity = "unicorn@microsoft.fr"
                PasswordFile = "c:\Securestring.txt"
                Authentication = "Basic"
                ConnectionUri = "https://outlook.office365.com/powershell-liveid/"
                Cmdlet = @('Set-Mailbox')
                SessionName = "Exchange"
            }
            Connect-Exchange -Config $ExchangeConfig

        .EXAMPLE
            #----------------------------------------------
            # With SecureString file + Key
            #----------------------------------------------
            $ExchangeConfig =@{
                Identity = "unicorn@microsoft.fr"
                PasswordFile = "C:\Securestring.txt"
                KeyFile = "C:\MyCertificat.key"
                Authentication = "Basic"
                ConnectionUri = "https://outlook.office365.com/powershell-liveid/"
                Cmdlet = @('Set-Mailbox')
                SessionName = "Exchange"
            }
            Connect-Exchange -Config $ExchangeConfig

    #>

    Param (
        [parameter(Mandatory=$true)]
        [Array]$Config
    )

    Try {

        # Load Credential
        if(-not([string]::IsNullOrEmpty($Config.PasswordFile)))
        {
            if(-not([string]::IsNullOrEmpty($Config.KeyFile)))
            {
                $Methode = "SecureString file + Key"
                $Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Config.Identity, (Get-Content $Config.PasswordFile | ConvertTo-SecureString -Key (Get-Content $Config.KeyFile))
            } # END Credential with Key File
            else
            {
                $Methode = "SecureString file"
                $Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Config.Identity, (Get-Content $Config.PasswordFile | ConvertTo-SecureString)
            } # END Credential without Key File
        }
        else
        {
            $Methode = "Plain password"
            $Secpasswd = ConvertTo-SecureString $Config.Password -AsPlainText -Force
            $Credential = New-Object -TypeName System.Management.Automation.PSCredential ($Config.Identity, $secpasswd)
        } # END Credential with plain password

        # Connect to Exchange
        $error.clear();
        Write-Debug "Attempting Connection to Exchange Online with $Methode"
        $SessionOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck 
        $Session = New-PSSession -Name $Config.SessionName -ConfigurationName Microsoft.Exchange -ConnectionUri $Config.ConnectionUri -Credential $Credential -Authentication $Config.Authentication -SessionOption $SessionOptions -AllowRedirection
        
        # Import Session
        Write-Debug "Importing Exchange Session"
        if(-not([string]::IsNullOrEmpty($Config.Cmdlet)))
        {
            Import-Module (Import-PSSession $Session -AllowClobber -CommandName $Config.Cmdlet -DisableNameChecking ) -Global -DisableNameChecking
        } else {
            Import-Module (Import-PSSession $Session -AllowClobber -DisableNameChecking) -Global -DisableNameChecking
        } # END Cmdlet selection

        # Error Management
        If ($error)
        {
            Write-warning "Unable to import Exchange PS session : $Error"
            return $false
        }#END Error
        else
        {
            Write-Debug "Connected to Exchange"

            # Set the Start time for the current session
            Set-Variable -Scope 'Global' -Name 'ExchangeSessionStartTime' -Value (Get-Date)

            return $true
        }#END Success
    } Catch {
        Write-Error "Unable to connect to Exchange $_"
        return $false
    }
}

Function Test-ExchangeSession {
    <#
    .SYNOPSIS
        Simple function to test exchange session

    .NOTES
        Author : Thomas ILLIET, contact@thomas-illiet.fr
        Date : 2017-08-01
        Last Update : 2017-08-01
        Test Date : 2018-01-10
        Version : 1.1.0
        
    .PARAMETER Config
        Exchange connexion config

    .PARAMETER ManualThrottle
        Manual throttle value then sleep for that many milliseconds

    .PARAMETER ActiveThrottle
        Amount of time gt our reset seconds then tear the session down and recreate it

    .PARAMETER Reconnect

    .EXAMPLE
        $ExchangeConfig =@{
            Identity = "unicorn@microsoft.fr"
            Password = "BeatifullUnicorne!"
            Authentication = "Basic"
            ConnectionUri = "https://outlook.office365.com/powershell-liveid/"
            Cmdlet = @('Set-Mailbox')
            SessionName = "Exchange"
        }
        Test-ExchangeSession -Config $ExchangeConfig -Reconnect 500

    #>

    Param (
       [parameter(Mandatory=$true)]
        [Array]$Config,
        [parameter(Mandatory=$false)]
        [int]$ManualThrottle = 0,
        [parameter(Mandatory=$false)]
        [double]$ActiveThrottle = .25,
        [parameter(Mandatory=$false)]
        [int]$Reconnect = 870
    )
    
    # Get the time that we are working on this object to use later in testing
    $ObjectTime = Get-Date
    
    # Reset and regather our session information
    $SessionInfo = $null
    $SessionInfo = Get-PSSession -Name $Config.SessionName -ErrorAction SilentlyContinue
    
    # Make sure we found a session
    if ($SessionInfo -eq $null) {
        write-debug "| ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"
        write-debug "| + Test Exchange Session"
        write-debug "| ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"
        Write-Debug "| + No Session Found"
        Write-Debug "| + Recreating Session"
        Invoke-ConnectExchange -Config $ExchangeConfig
    }    

    # Make sure it is in an opened state if not log and recreate
    elseif ($SessionInfo.State -ne "Opened"){
        write-debug "| ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"
        write-debug "| + Test Exchange Session"
        write-debug "| ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"
        Write-Debug "| + Session not in Open State"
        Write-Debug "| + Recreating Session"
        Get-PSSession -Name $Config.SessionName | Remove-PSSession -Confirm:$false
        New-Sleep 10 "Waitung for reconnect..."
        Invoke-ConnectExchange -Config $ExchangeConfig

    }

    # If we have looped thru objects for an amount of time gt our reset seconds then tear the session down and recreate it
    elseif (($ObjectTime - $ExchangeSessionStartTime    ).totalseconds -gt $Reconnect){
        write-debug "| ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"
        write-debug "| + Test Exchange Session"
        write-debug "| ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"
        Write-Debug "| + Session has been active for greater than given number of seconds"
        Write-Debug "| + Rebuilding Connection"
        
        # Estimate the throttle delay needed since the last session rebuild
        # Amount of time the session was allowed to run * our activethrottle value
        # Divide by 2 to account for network time, script delays, and a fudge factor
        # Subtract 15s from the results for the amount of time that we spend setting up the session anyway
        [int]$DelayinSeconds = ((($Reconnect * $ActiveThrottle) / 2) - 15)
        
        # If the delay is >15s then sleep that amount for throttle to recover
        if ($DelayinSeconds -gt 15){
            Write-Debug "| + Sleeping some addtional seconds to allow throttle recovery"
            New-Sleep $DelayinSeconds "Sleeping some addtional seconds to allow throttle recovery"
        }
                
        # new O365 session and reset our object processed count
        Get-PSSession -Name $Config.SessionName | Remove-PSSession -Confirm:$false
        New-Sleep 15 "Waitung for reconnect..."
        Invoke-ConnectExchange -Config $ExchangeConfig

    }
    else {
        # If session is active and it hasn't been open too long then do nothing and keep going
    }
    
    # If we have a manual throttle value then sleep for that many milliseconds
    if ($ManualThrottle -gt 0){
        Write-Debug "| + Sleeping $ManualThrottle milliseconds"
        Start-Sleep -Milliseconds $ManualThrottle
    }
}

function New-Sleep {
    <#
        .SYNOPSIS
            Suspends the activity in a script or session for the specified period of time.
        .DESCRIPTION
            The New-Sleep cmdlet suspends the activity in a script or session for the specified period of time.
            You can use it for many tasks, such as waiting for an operation to complete or pausing before repeating an operation.
        .NOTES
            File Name : New-Sleep.ps1
            Author : Thomas ILLIET, contact@thomas-illiet.fr
            Date : 2017-05-10
            Last Update : 2018-01-08
            Version : 1.0.2
        .PARAMETER S
            Time to wait
        .PARAMETER Message
            Message you want to display
        .EXAMPLE
            New-Sleep -S 60 -Message "wait and see"
        .EXAMPLE
            New-Sleep -S 60
    #>

    [cmdletbinding()]
    param
    (
        [parameter(Mandatory=$true)]
        [int]$S,
        [parameter(Mandatory=$false)]
        [string]$Message="Wait"
    )
    for ($i = 1; $i -lt $s; $i++) 
    {
        if ($host.ui.RawUi.KeyAvailable) { # Cancel waiting if CTRL+Q is pressed
            $key = $host.ui.RawUI.ReadKey("NoEcho,IncludeKeyUp") 
            if (($key.VirtualKeyCode -eq 81) -AND ($key.ControlKeyState -match "LeftCtrlPressed"))
            {
                break
            }
        }
        [int]$TimeLeft = $s - $i
        Write-Progress -Activity $message -PercentComplete (100 / $s * $i) -CurrentOperation "$TimeLeft seconds left" -Status "Please wait (Cancel with CTRL + Q)"
        Start-Sleep -s 1
    }
    Write-Progress -Completed $true -Status "Please wait"
}