tak.exchange.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
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
#region Helpers

function New-ExchangeAutodiscoverReport {
    [CmdletBinding()]
    param (
        [psobject]
        $InputObject,
        [System.IO.FileInfo]
        $FileName = (Join-Path -Path $($env:temp) -ChildPath "ExchangeAutodiscoverReport.html")
    )
    
    begin {
        Remove-Item -Path $FileName -ErrorAction SilentlyContinue
    }
    
    process {
        foreach($key in (Get-Member -InputObject $InputObject -MemberType NoteProperty).Name) {
            $html += $InputObject.$key.Protocol | ConvertTo-Html -As List -Fragment
        }
        $html | Set-Content -Path $FileName
        Write-Host "report at $FileName"
    }
    
    end {
    }
}

function Get-XmlBody([string]$EmailAddress){
[xml]@"
    <Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">
    <Request>
        <EMailAddress>$EmailAddress</EMailAddress>
        <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
    </Request>
    </Autodiscover>
"@

}

function Get-AutodiscoverURI {
    param (
        [string]$EmailAddress,
        [string]$ComputerName,
        [switch]$ExcludeExplicitO365Endpoint
    )
    $domainName = $EmailAddress -split "@" | Select-Object -Last 1
    $out = @{
        "root" = "https://$domainName/autodiscover/autodiscover.xml";
        "autodiscover" = "https://autodiscover.$domainName/autodiscover/autodiscover.xml";
    }
    
    if($adSrv = Get-AutodiscoverSRV -domainName $domainName) {
        $out.Add("srv",$adSrv)
    }
    
    if($ComputerName) {
        $out.Add("uri","https://$ComputerName/autodiscover/autodiscover.xml")
    }
    
    if (-not($ExcludeExplicitO365Endpoint)) {
        $out.Add("o365","https://autodiscover-s.outlook.com/autodiscover/autodiscover.xml")
    }

    $out
}

function Get-AutodiscoverResponse {
    [CmdletBinding()]
    param(
        [string]$uri,
        [xml]$body,
        [int]$Timeout = 4,
        [pscredential]$Credential
    )
    $params = @{
        "uri" = $uri 
        "Credential" = $Credential
        "Method" = "post" 
        "Body" = $body 
        "Headers" = @{"content-type"="text/xml"} 
        "DisableKeepAlive" = $true
        "TimeoutSec" = $Timeout
    }
    try {
        Write-Verbose "Trying to connect to $uri"
        $adPayload = Invoke-RestMethod @params
        $adPayload.Autodiscover.Response.Account         
    } catch {
        Write-Verbose "Could not connect to $uri"
    }
}   

function Get-AutodiscoverSRV([string]$domainName) {
    $dns = Resolve-DnsName -Name "_autodiscover._tcp.$domainName" -Type SRV -ErrorAction SilentlyContinue | Where-Object {$_.Type -eq "SRV" -and $_.Port -eq 443}
    if($dns) {
        "https://$($dns.NameTarget)/autodiscover/autodiscover.xml"
    }
}
#endregion Helpers

#region Exchange

function Test-ExchangeAutodiscover {
    <#
    .SYNOPSIS
        Test Exchange Autodiscover Web Service.
    .DESCRIPTION
        This function tests the Exchange Autodiscover Web Serivce for a given Emailaddress. If ComputerName is not specified,
        the function tries to look up the Autodiscover service using the Outlook Clients logic. Locally cached and SCP data
        are not evaluated.
    .EXAMPLE
        PS C:\> Test-ExchangeAutodiscover thomas@ntsystems.it -Credential (Get-Credential)
         
        This example tests the Autodiscover service for the given mailbox. It will query dns for autodiscover.ntsystems.it and _autodiscover._tcp.ntsystems.it.
        It will then try to retrieve an Autodiscover payload from https://ntsystems.it, https://autodiscover.ntsystems.it and the Office 365 endpoint.
    .OUTPUTS
        [psobject]
    #>
    
    [CmdletBinding(HelpUri = 'https://ntsystems.it/PowerShell/TAK/test-exchangeautodiscover/')]
    param (
        [string]
        $EmailAddress,
        [string]
        $ComputerName,
        [pscredential]
        $Credential,
        [switch]
        $ExcludeExplicitO365Endpoint,
        [System.IO.FileInfo]
        $Report
    )
    
    begin {
        # Get URI and XML Body for reuqest
        $adURIs = Get-AutodiscoverURI @PSBoundParameters
        $body = Get-XMLBody @PSBoundParameters
    }
    
    process {
        # Create an empty dictionary to store
        $out = @{}
        foreach($key in $adURIs.keys){
            Write-Verbose "Testing $key domain for $EmailAddress : $($adURIs[$key])"
            $r = Get-AutodiscoverResponse -uri $adURIs[$key] -Credential $Credential -body $body
            if($r) {
                $out.add($key,$r)
            }
        }
        # create a new object and write it to the pipeline
        $global:ExchangeAutodiscoverResults = New-Object -TypeName psobject -Property $out | Add-Member -TypeName 'System.TAK.ExchangeAutoDiscover' -PassThru
        Write-Output $global:ExchangeAutodiscoverResults
        
        Write-Host "Results are available through the global variable `$ExchangeAutodiscoverResults for your convenience.`n"

        if($Report) {
            New-ExchangeAutodiscoverReport -InputObject $global:ExchangeAutodiscoverResults -FileName $Report
        }

    }
    
    end {
    }
}



#endregion


#region Test ADFS
function Test-FederationService {
    <#
    .Synopsis
    Test the ADFS web service
    .DESCRIPTION
    This function uses Invoke-RestMethod to test if the federation service metadata can be retrieved from a given server.
    .EXAMPLE
    Test-FederationService -ComputerName fs.uclab.eu
    This example gets federation service xml information over the server fs.uclab.eu
    #>

    [CmdletBinding(HelpUri = 'https://ntsystems.it/PowerShell/TAK/Test-FederationService/')]
    param(
        # Specifies the name of the federation server
        [Parameter(Mandatory=$true)]
        [validateLength(3,255)]
        [validatepattern("\w\.\w")]
        [string]
        [Alias("Server")]
        $ComputerName
    )

    $uri = "https://$ComputerName/FederationMetadata/2007-06/FederationMetadata.xml"
    # "adfs/ls/idpinitiatedsignon.htm"
    try {
        $webRequest = Invoke-RestMethod -Uri $uri -ErrorAction Stop
        Write-Verbose $webRequest
    } catch {
        Write-Warning "Could not connect to $uri error $_"
        return
    }

    [byte[]]$rawData = [System.Convert]::FromBase64String($webRequest.EntityDescriptor.Signature.KeyInfo.X509Data.X509Certificate)
    $certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
    $certificate.Import($rawData)
    
    $out = [ordered]@{
        "entityID" = $webRequest.entitydescriptor.entityID
        "xmlns" = $webRequest.entitydescriptor.xmlns
        "Roles" = @{
            "type" = $webRequest.entitydescriptor.RoleDescriptor.type
            "ServiceDisplayName" = $webRequest.entitydescriptor.RoleDescriptor.ServiceDisplayName
        }
        "IDPSSODescriptor" = $webRequest.EntityDescriptor.IDPSSODescriptor
        "SPSSODescriptor" = $webRequest.EntityDescriptor.SPSSODescriptor
        "SigningCert" = $certificate

    }
    # Create a custom object and add a custom TypeName for formatting before writing to pipeline
    Write-Output (New-Object -TypeName psobject -Property $out) 
}
#endregion Test ADFS


#region SPF
function New-SPFRecord {
    <#
    .SYNOPSIS
        Create SPF record for a given mail domain.
    .DESCRIPTION
        This function helps with creating SPF records for mail domains.
        The SPF record should look something like this:
 
        v=spf1 mx a ptr ip4:127.1.1.1/24 a:host.example.com include:example.com -all
 
        More information: https://www.ietf.org/rfc/rfc4408.txt
    .EXAMPLE
        PS C:\> Get-AcceptedDomain | New-SPFRecord -mx -IncludeDomain spf.protection.outlook.com -IncludeIP 192.0.2.1,2001:DB8::1 -Action Fail
 
        DomainName : uclab.eu
        Record : "v=spf1 mx ip4:192.0.2.1 ip6:2001:DB8::1 include:spf.protection.outlook.com -all"
 
        The above example creates SPF records for all accepted domains in Exchange (Online).
    .INPUTS
        [string]
        [AcceptedDomain]
 
        This function accepts a string or objects with a DomainName property (such as returned by Get-AcceptedDomain) as input.
    .OUTPUTS
        [psobject]
 
        This function writes a custom object to the pipeline.
    .NOTES
        Author: @torggler
    #>

    [CmdletBinding(HelpUri = 'https://ntsystems.it/PowerShell/TAK/New-SPFRecord/')]
    param (
        [Parameter(ValueFromPipelineByPropertyName=$true,
            ValueFromPipeline=$true)]
        [string]
        $DomainName,
        [switch]
        $mx,
        [switch]
        $a,
        [switch]
        $ptr,
        [ipaddress[]]
        $IncludeIP,
        [string]
        $IncludeDomain,
        [string]
        $IncludeHost,
        [ValidateSet("Fail","SoftFail","Neutral")]
        [string]
        $Action = "Fail"
    )   
    process {
        # if run without parameters, set mx to default
        if(-not($PSBoundParameters.Count)) {
            $PSBoundParameters.Add("mx",$true)
            $PSBoundParameters.Add("Action","Fail")
        }

        Write-Verbose "Creating SPF for domain $DomainName"

        $spfBase = "v=spf1"

        switch ($PSBoundParameters.Keys) {
            "mx" { $spfMX = "mx" }
            "ptr" { $spfPTR = "ptr" }
            "a" { $spfA = "a" }
            "IncludeIP" { 
                $i = 0 # use a little counter to avoid inserting unnecessary spaces
                foreach ($ip in $IncludeIP) {
                    switch ($ip.AddressFamily) {
                        InterNetwork { 
                            Write-Verbose "adding ip4 $ip"
                            if($i -eq 0) {
                                $spfIP = "ip4:$($ip.IPAddressToString)" 
                            } else {
                                $spfIP += " ip4:$($ip.IPAddressToString)" 
                            }
                        }
                        InterNetworkV6 { 
                            Write-Verbose "adding ip6 $ip"
                            if($i -eq 0) {
                                $spfIP = "ip6:$($ip.IPAddressToString)"
                            } else {
                                $spfIP += " ip6:$($ip.IPAddressToString)" 
                            }
                        }
                    }
                $i++
                }
            }
            "IncludeDomain" {
                Write-Verbose "adding include:$IncludeDomain"
                $spfDomain = "include:$IncludeDomain"
            }
            "IncludeHost" {
                Write-Verbose "adding include:$IncludeHost"
                $spfHost = "a:$IncludeHost"
            }
            "Action" {
                switch($Action) {
                    "Fail" { 
                        Write-Verbose "Setting action to Fail"
                        $spfAction = "-all" 
                    }
                    "SoftFail" {
                        Write-Verbose "Setting action to SoftFail"
                        $spfAction = "~all"
                    }
                    "Neutral" { 
                        Write-Verbose "Setting action to Neutral"
                        $spfAction = "?all" 
                    }
                }
            }
        }
        $Record = $spfBase,$spfMX,$spfA,$spfPTR,$spfIP,$spfDomain,$spfHost,$spfAction | Where-Object {$_}
        New-Object -TypeName psobject -Property ([ordered]@{
            DomainName = $DomainName
            Record = $Record -join " "
        })        
    }
}


#endregion SPF