Friday, April 25, 2014

Slide deck from my MEC Presentation "Using exchange as a platform for innovation"

The slide deck from my MEC presentation "Using exchange as a platform for innovation" has just been made available on http://video.ch9.ms/sessions/mec/2014/EXTIN401_Scales.pptx  . I should have the GPX mail app posted up soon when i sort out the tile issue out. Any venture capitalist with money to loose on creating crazy/interesting/unique devices that connect to Exchange mailboxes please watch the videos in the presentation (got plenty of ideas in the pipeline) and let me know :) .

(One typo in the PPX is I managed to create a new EWS operation "FindPerson" should have been FindPeople http://msdn.microsoft.com/en-us/library/office/jj191039(v=exchg.150).aspx March was a long month)...

Oneliner Mailbox access with Powershell in Exchange Online using the oData preview

One of the things that was announced at MEC recently was Microsoft's Cloud first strategy with Exchange, which basically means that new feature will appear in Exchange Online first and then at some later date make their way into the OnPremise version of Exchange. One of these new cloud first features is the OData API for Mailbox data which is a REST based API for accessing mailbox data (as apposed to EWS which is a SOAP based API for accessing mailbox data). JSON and REST have become the standard for building WebAPI's over the past few years and every man and his dog (Google, Facebook,Twitter,Apple etc) are now using this so its exciting to see Exchange make this move.

If you want to find out more about it and you have a couple of hours to watch some good video's I would check the following two presentations from MEC http://channel9.msdn.com/Events/MEC/2014/EXT301 and  http://channel9.msdn.com/Events/MEC/2014/EXT304 which will give you a good grounding in both oData  and also the new consent framework which is important if your building modern apps. 

One of the interesting things your can do with these REST API is using the Invoke-RestMethod cmdlet in Powershell v4 is now get access to Mailbox data with one line of code. Here are a few samples to wet your appetite and get you started (note this API is still in preview so this is subject to change/errors and bugs)

Show Unread Inbox Messages


  1. Invoke-RestMethod -Uri "https://outlook.office365.com/ews/odata/Me/Inbox/Messages?`$filter=IsRead eq false" -Credential (get-credential) | foreach-object{$_.value | select Subject}  

Show Calendar Appointments for the next 7 days


  1. Invoke-RestMethod -Uri ("https://outlook.office365.com/ews/odata/Me/Calendar/Events?`$filter=Start le " + (Get-Date).ToUniversalTime().AddDays(7).ToString("yyyy-MM-ddThh:mm:ssZ") + " and End ge " + (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddThh:mm:ssZ")) -Credential (Get-Credential) | foreach-object{$_.Value}  

Show Contacts


  1. Invoke-RestMethod -Uri "https://outlook.office365.com/ews/odata/Me/Inbox/Messages" -Credential (get-credential) | foreach-object{$_.value}  

There are lots more example of using the new REST commands at http://msdn.microsoft.com/en-us/library/office/dn605892(v=office.15).aspx


Wednesday, April 16, 2014

Quota if Mailbox Size script for Exchange 2010,2013

If your playing around with different Mailbox quota values you might want to see what the usage of those quotas would be before you apply them to a Mailbox. This is a very simple Mailbox Size script for Remote Powershell to show you this with a funky console graph output (and a CSV report) eg


So what the script does is the normal Get-Mailbox | Get-MailboxStatistics to get the TotalSize of the Mailbox in MB and compares that against a QuotaIf Value you feed into script in the above example that was 2000 MB. It then works out the current percentage used and the produces an Alt-ASCII graph and the above table.

When you run this script you need to feed in the QuotaIf value eg

.\quotaIf.ps1 2000

I've put a download of this script here and code itself looks like

  1. $QuotaIfVal = $args[0]  
  2. $Script:rptCollection = @()  
  3. get-mailbox -ResultSize unlimited| Get-MailboxStatistics | foreach-object{  
  4.     $rptObj = "" | Select MailboxName,TotalSize,QuotaIfPercent,PercentGraph  
  5.     $rptObj.MailboxName = $_.DisplayName  
  6.     [Int64]$rptObj.TotalSize = ($_.TotalItemSize.ToString() |%{($_.Substring($_.indexof("(")+1,$_.indexof("b")-$_.indexof("(")-2)) -replace(",","")})/1MB  
  7.   
  8.     $rptObj.QuotaIfPercent = 0    
  9.     if($rptObj.TotalSize -gt 0){  
  10.         $rptObj.QuotaIfPercent = [Math]::round((($rptObj.TotalSize/$QuotaIfVal) * 100))   
  11.     }  
  12.     $PercentGraph = ""  
  13.     for($intval=0;$intval -lt 100;$intval+=4){  
  14.         if($rptObj.QuotaIfPercent -gt $intval){  
  15.             $PercentGraph += "▓"  
  16.         }  
  17.         else{         
  18.             $PercentGraph += "░"  
  19.         }  
  20.     }  
  21.     $rptObj.PercentGraph = $PercentGraph   
  22.     $rptObj | fl  
  23.     $Script:rptCollection +=$rptObj   
  24. }  
  25. $Script:rptCollection  
  26. $Script:rptCollection | Export-Csv -NoTypeInformation -Path c:\temp\QuotaIfReport.csv -Encoding UTF8   

Using EWS and AQS to check number of Unread in X days with Graph

It seems that you can never have too many ways of reading the number of Unread messages in a Inbox so here is another method. The following script uses two AQS queries to work out first the number of messages in the Inbox from a prescribed period eg the last 30 days and then the number of these that are then unread. It then works out the percentage of unread using of the two values and then creates a graph to display in the Console using Alt-ASCII characters which produces a pretty nifty output eg


It also produces a CSV output eg


AQS queries will work in Exchange 2010 and 2013 so this script should work okay in those environments as well as ExchangeOnline.

I've created two versions of this script, the first version just reports on one user so you run it with the email of the users you want to report on and the number of days to query back. eg

.\AQSUsr.ps1 gscales1@msgdevelop.onmicrosoft.com 30

the second version reports on all of the users in a CSV file and uses EWS Impersonation to access the Mailboxes. To use the script you need to have configured EWS Impersonation and feed the script a CSV file of the users you want to report on eg the CSV file should look like

smtpaddress
user1@domain.com
user2@domain.com

and you run it like

.\AQSAllUsr.ps1 ./users.csv 30

I've put a download of both scripts here the code look like


  1. ## Get the Mailbox to Access from the 1st commandline argument  
  2.   
  3. $csvFileName = $args[0]  
  4. $DaysBack = $args[1]  
  5.   
  6. ## Load Managed API dll    
  7. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll"    
  8.     
  9. ## Set Exchange Version    
  10. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2    
  11.     
  12. ## Create Exchange Service Object    
  13. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
  14.     
  15. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
  16.     
  17. #Credentials Option 1 using UPN for the windows Account    
  18. $psCred = Get-Credential    
  19. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
  20. $service.Credentials = $creds        
  21.     
  22. #Credentials Option 2    
  23. #service.UseDefaultCredentials = $true    
  24.     
  25. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
  26.     
  27. ## Code From http://poshcode.org/624  
  28. ## Create a compilation environment  
  29. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  30. $Compiler=$Provider.CreateCompiler()  
  31. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  32. $Params.GenerateExecutable=$False  
  33. $Params.GenerateInMemory=$True  
  34. $Params.IncludeDebugInformation=$False  
  35. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  36.   
  37. $TASource=@' 
  38.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  39.     public class TrustAll : System.Net.ICertificatePolicy { 
  40.       public TrustAll() {  
  41.       } 
  42.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  43.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  44.         System.Net.WebRequest req, int problem) { 
  45.         return true; 
  46.       } 
  47.     } 
  48.   } 
  49. '@   
  50. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  51. $TAAssembly=$TAResults.CompiledAssembly  
  52.   
  53. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  54. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  55. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  56.   
  57. ## end code from http://poshcode.org/624  
  58.     
  59. ## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use    
  60.     
  61. #CAS URL Option 1 Autodiscover    
  62. #$service.AutodiscoverUrl($MailboxName,{$true})    
  63. #"Using CAS Server : " + $Service.url     
  64.      
  65. #CAS URL Option 2 Hardcoded    
  66.     
  67. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
  68. #$service.Url = $uri      
  69.     
  70. ## Optional section for Exchange Impersonation    
  71.     
  72.   
  73. $Script:rptCollection = @()  
  74. function Process-Mailbox{    
  75.     param (    
  76.             $SmtpAddress = "$( throw 'SMTPAddress is a mandatory Parameter' )"    
  77.           )    
  78.     process{    
  79.         Write-Host ("Processing Mailbox : " + $SmtpAddress)    
  80.   
  81. # Bind to the Inbox Folder  
  82. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$SmtpAddress)     
  83. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  84. $Range = [system.DateTime]::Now.AddDays(-$DaysBack).ToString("MM/dd/yyyy") + ".." + [system.DateTime]::Now.AddDays(1).ToString("MM/dd/yyyy")     
  85. $AQSString1 = "System.Message.DateReceived:" + $Range     
  86. $AQSString2 = "(" + $AQSString1 + ") AND (isread:false)"   
  87. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1)    
  88. $allMail = $Inbox.FindItems($AQSString1,$ivItemView)  
  89. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1)   
  90. $unread = $Inbox.FindItems($AQSString2,$ivItemView)  
  91. $rptObj = "" | Select MailboxName,TotalCount,Unread,PercentUnread,PercentGraph  
  92.   
  93. #Write-Host ("All Mail " + $allMail.TotalCount)   
  94. #Write-Host ("Unread " + $unread.TotalCount)   
  95. $rptObj.MailboxName = $SmtpAddress  
  96. $rptObj.TotalCount = $allMail.TotalCount  
  97. $rptObj.Unread = $unread.TotalCount   
  98. $PercentUnread = 0  
  99. if($unread.TotalCount -gt 0){  
  100.     $PercentUnread = [Math]::round((($unread.TotalCount/$allMail.TotalCount) * 100))  
  101. }  
  102. $rptObj.PercentUnread = $PercentUnread   
  103. #Write-Host ("Percent Unread " + $PercentUnread)  
  104. $ureadGraph = ""  
  105. for($intval=0;$intval -lt 100;$intval+=4){  
  106.     if($PercentUnread -gt $intval){  
  107.         $ureadGraph += "▓"  
  108.     }  
  109.     else{         
  110.         $ureadGraph += "░"  
  111.     }  
  112. }  
  113. #Write-Host $ureadGraph  
  114. $rptObj.PercentGraph = $ureadGraph  
  115. $rptObj | fl  
  116. $Script:rptCollection +=$rptObj  
  117.   
  118. }  
  119. }  
  120. Import-Csv -Path $csvFileName | ForEach-Object{    
  121.     if($service.url -eq $null){    
  122.         $service.AutodiscoverUrl($_.SmtpAddress,{$true})     
  123.         "Using CAS Server : " + $Service.url     
  124.     }    
  125.     Try{    
  126.         $service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $_.SmtpAddress)   
  127.   
  128.         Process-Mailbox -SmtpAddress $_.SmtpAddress    
  129.     }    
  130.     catch{    
  131.         Write-host ("Error processing Mailbox : " + $_.SmtpAddress + $_.Exception.Message.ToString())    
  132.         $Error.Clear()  
  133.     }    
  134. }    
  135. $Script:rptCollection | ft  
  136. $Script:rptCollection | Export-Csv -NoTypeInformation -Path c:\temp\InboxUnreadReport.csv -Encoding UTF8   

Friday, April 11, 2014

Pull Subscription Mailbox Item move tracking script MEC sample 2

This is the second of the Powershell script samples from my MEC talk last week. The idea behind this script is to track the movement of Items in a Mailbox between folders using Pull Notifications in EWS, you can read more about how Pull notifications work here .  This script takes advantage of the fact that when you enable Single Item Recovery any deletes you make in a Mailbox (hard or soft) the Items aren't deleted rather moved to the Dumpster v2 RecoverableItems folders. So if you track all the move notification events you can track both the movement of Messages by rules, users or deletes. eg the results look like



I've created two different versions of this script, the first version subscribes to all folders in a Mailbox and runs continuously against the Mailbox and does a GetEvents request every minute to retrieve the latest events from all folders in a Mailbox and then logs that to file.

The second version just subscribes to the Inbox folder and saves the subscription information out to file when you run it the first time. Then the next time you run the script it will use the saved SubscriptionId and Watermark value to get all the events since the script was last run. Pull Subscriptions have a timeout of 1440 minutes (which is 1 day) so if you are going to run this version you need to run it with a maximum gap of 1 day. The idea of this second version was just to track inbox fan-out of messages rather then subscribing to all folders like the first version.

Both of these scripts use EWS Impersonation which must be configured for the account your going to run the script as. When you run the script you need to pass in the Mailbox to run against and the timeout value in minutes for subscription which needs to between 1 and 1440 eg

./pullSubTrackWm.ps1 jcool@msgdevelop.onmicrosoft.com 1440

I've put a download of both scripts here the code itself looks like

  1. ## Get the Mailbox to Access from the 1st commandline argument  
  2. $MailboxName = $args[0]  
  3. $duration = $args[1]  
  4. $sw = [system.diagnostics.stopwatch]::startNew()  
  5. $Error.Clear()  
  6. $Script:changeLog = "c:\temp\ChangeLog-$(get-date -f yyyy-MM-dd).csv";   
  7. if(-Not(Test-Path -Path $Script:changeLog)){  
  8.     Add-Content -Path $Script:changeLog ("EventTime,MessageDateTimeReceived,Subject,MovedFrom,MovedTo,LastModifiedName")  
  9. }  
  10. #Add-Content -Path $Script:changeLog ("Start Log" + (Get-Date).ToString())   
  11.   
  12. ## Load Managed API dll    
  13. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll"    
  14. Add-Type -AssemblyName System.Runtime.Serialization  
  15. ## Set Exchange Version    
  16. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2    
  17.     
  18. ## Create Exchange Service Object    
  19. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
  20.     
  21. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
  22.     
  23. #Credentials Option 1 using UPN for the windows Account    
  24. $psCred = Get-Credential    
  25. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
  26. $service.Credentials = $creds        
  27.     
  28. #Credentials Option 2    
  29. #service.UseDefaultCredentials = $true    
  30.     
  31. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
  32.     
  33. ## Code From http://poshcode.org/624  
  34. ## Create a compilation environment  
  35. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  36. $Compiler=$Provider.CreateCompiler()  
  37. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  38. $Params.GenerateExecutable=$False  
  39. $Params.GenerateInMemory=$True  
  40. $Params.IncludeDebugInformation=$False  
  41. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  42.   
  43. $TASource=@' 
  44.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  45.     public class TrustAll : System.Net.ICertificatePolicy { 
  46.       public TrustAll() {  
  47.       } 
  48.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  49.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  50.         System.Net.WebRequest req, int problem) { 
  51.         return true; 
  52.       } 
  53.     } 
  54.   } 
  55. '@   
  56. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  57. $TAAssembly=$TAResults.CompiledAssembly  
  58.   
  59. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  60. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  61. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  62.   
  63. ## end code from http://poshcode.org/624  
  64.     
  65. ## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use    
  66.     
  67. #CAS URL Option 1 Autodiscover    
  68. $service.AutodiscoverUrl($MailboxName,{$true})    
  69. "Using CAS Server : " + $Service.url     
  70.   
  71.   
  72. #CAS URL Option 2 Hardcoded    
  73.     
  74. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
  75. #$service.Url = $uri      
  76.     
  77. ## Optional section for Exchange Impersonation    
  78.     
  79. $service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
  80. #check Anchor header for Exchange 2013/Office365    
  81. if($service.HttpHeaders.ContainsKey("X-AnchorMailbox")){    
  82.     $service.HttpHeaders["X-AnchorMailbox"] = $MailboxName    
  83. }else{    
  84.     $service.HttpHeaders.Add("X-AnchorMailbox"$MailboxName);    
  85.     $service.HttpHeaders.Add("X-PreferServerAffinity","true");  
  86. }    
  87. "AnchorMailbox : " + $service.HttpHeaders["X-AnchorMailbox"]    
  88.   
  89.   
  90.   
  91. $FolderCollection = New-Object System.Collections.Hashtable  
  92. #Define Function to convert String to FolderPath    
  93. function ConvertToString($ipInputString){    
  94.     $Val1Text = ""    
  95.     for ($clInt=0;$clInt -lt $ipInputString.length;$clInt++){    
  96.             $Val1Text = $Val1Text + [Convert]::ToString([Convert]::ToChar([Convert]::ToInt32($ipInputString.Substring($clInt,2),16)))    
  97.             $clInt++    
  98.     }    
  99.     return $Val1Text    
  100. }   
  101.   
  102. #Define Extended properties    
  103. $PR_FOLDER_TYPE = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(13825,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);    
  104. $folderidcnt = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)    
  105. #Define the FolderView used for Export should not be any larger then 1000 folders due to throttling    
  106. $fvFolderView =  New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)    
  107. #Deep Transval will ensure all folders in the search path are returned    
  108. $fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep;    
  109. $psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)    
  110. $PR_Folder_Path = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(26293, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);    
  111. #Add Properties to the  Property Set    
  112. $psPropertySet.Add($PR_Folder_Path);    
  113. $fvFolderView.PropertySet = $psPropertySet;    
  114. #The Search filter will exclude any Search Folders    
  115. $sfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($PR_FOLDER_TYPE,"1")    
  116. $fiResult = $null    
  117. #The Do loop will handle any paging that is required if there are more the 1000 folders in a mailbox    
  118. do {    
  119.     $fiResult = $Service.FindFolders($folderidcnt,$sfSearchFilter,$fvFolderView)    
  120.     foreach($ffFolder in $fiResult.Folders){    
  121.         $foldpathval = $null    
  122.         #Try to get the FolderPath Value and then covert it to a usable String     
  123.         if ($ffFolder.TryGetProperty($PR_Folder_Path,[ref] $foldpathval))    
  124.         {    
  125.             $binarry = [Text.Encoding]::UTF8.GetBytes($foldpathval)    
  126.             $hexArr = $binarry | ForEach-Object { $_.ToString("X2") }    
  127.             $hexString = $hexArr -join ''    
  128.             $hexString = $hexString.Replace("FEFF""5C00")    
  129.             $fpath = ConvertToString($hexString)    
  130.         }    
  131.         "FolderPath : " + $fpath    
  132.         $FolderCollection.Add($ffFolder.Id.UniqueId,$fpath)  
  133.     }   
  134.     $fvFolderView.Offset += $fiResult.Folders.Count  
  135. }while($fiResult.MoreAvailable -eq $true)    
  136.   
  137. function GetEventsRequest{    
  138. param (  
  139.   $SubscriptionId="$( throw 'SubscriptionId is a mandatory Parameter' )",  
  140.   $Watermark="$( throw 'Credentials is a mandatory Parameter' )",  
  141.   $ImpersonationHeader="$( throw 'ImpersonationHeader is a mandatory Parameter' )"  
  142. )  
  143. process{  
  144. Write-Host($SubscriptionId)  
  145. $request = @" 
  146. <?xml version="1.0" encoding="utf-8"?> 
  147. <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
  148.   xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> 
  149.    <soap:Header> 
  150.     <t:RequestServerVersion Version="Exchange2010_SP2"/> 
  151.     <t:ExchangeImpersonation> 
  152.       <t:ConnectingSID> 
  153.         <t:SmtpAddress>$ImpersonationHeader</t:SmtpAddress> 
  154.       </t:ConnectingSID> 
  155.     </t:ExchangeImpersonation> 
  156.   </soap:Header> 
  157.   <soap:Body> 
  158.     <GetEvents xmlns="http://schemas.microsoft.com/exchange/services/2006/messages"> 
  159.       <SubscriptionId>$SubscriptionId</SubscriptionId> 
  160.       <Watermark>$Watermark</Watermark> 
  161.     </GetEvents> 
  162.   </soap:Body> 
  163. </soap:Envelope> 
  164. "@  
  165. return $request  
  166. }  
  167. }  
  168. $DeletionsID = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::RecoverableItemsDeletions,$MailboxName);  
  169. $Deletions = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$DeletionsID)  
  170. $evEvents = new-object Microsoft.Exchange.WebServices.Data.EventType[] 6  
  171. $evEvents[0] = [Microsoft.Exchange.WebServices.Data.EventType]::Copied  
  172. $evEvents[1] = [Microsoft.Exchange.WebServices.Data.EventType]::Created  
  173. $evEvents[2] = [Microsoft.Exchange.WebServices.Data.EventType]::Deleted  
  174. $evEvents[3] = [Microsoft.Exchange.WebServices.Data.EventType]::Modified  
  175. $evEvents[4] = [Microsoft.Exchange.WebServices.Data.EventType]::Moved  
  176. $evEvents[5] = [Microsoft.Exchange.WebServices.Data.EventType]::NewMail  
  177. $psSub = ""  
  178. if(Test-Path -Path ("c:\temp\" + $MailboxName + "PullSubWM.wm")){ 
  179.     $psSub = Import-Csv -Path ("c:\temp\" + $MailboxName + "PullSubWM.wm") 
  180.     $request = GetEventsRequest -SubscriptionId $psSub.SubscriptionID -Watermark $psSub.Watermark -ImpersonationHeader $MailboxName 
  181.     $mbMailboxFolderURI = New-Object System.Uri($service.url)   
  182.     $wrWebRequest = [System.Net.WebRequest]::Create($mbMailboxFolderURI)   
  183.     $wrWebRequest.CookieContainer =  New-Object System.Net.CookieContainer    
  184.     $wrWebRequest.KeepAlive = $false;   
  185.     $wrWebRequest.Useragent = "EWS Script" 
  186.     $wrWebRequest.Headers.Set("Pragma", "no-cache");   
  187.     $wrWebRequest.Headers.Set("Translate", "f");   
  188.     $wrWebRequest.Headers.Set("Depth", "0");   
  189.     $wrWebRequest.ContentType = "text/xml";   
  190.     $wrWebRequest.ContentLength = $expRequest.Length;   
  191.     $wrWebRequest.Timeout = 60000;   
  192.     $wrWebRequest.Method = "POST";   
  193.     $wrWebRequest.Credentials = $creds   
  194.     $bqByteQuery = [System.Text.Encoding]::ASCII.GetBytes($request);   
  195.     $wrWebRequest.ContentLength = $bqByteQuery.Length;   
  196.     $rsRequestStream = $wrWebRequest.GetRequestStream();   
  197.     $rsRequestStream.Write($bqByteQuery, 0, $bqByteQuery.Length);   
  198.     $rsRequestStream.Close();   
  199.     $wrWebResponse = $wrWebRequest.GetResponse();   
  200.     $rsResponseStream = $wrWebResponse.GetResponseStream()   
  201.     $sr = new-object System.IO.StreamReader($rsResponseStream);   
  202.     $rdResponseDocument = New-Object System.Xml.XmlDocument   
  203.     $rdResponseDocument.LoadXml($sr.ReadToEnd()); 
  204.     $rdResponseDocument.Envelope.Body.GetEventsResponse.ResponseMessages.GetEventsResponseMessage.ResponseClass 
  205.     if($rdResponseDocument.Envelope.Body.GetEventsResponse.ResponseMessages.GetEventsResponseMessage.ResponseClass -eq "Error"){ 
  206.          
  207.         $rdResponseDocument.Envelope.Body.GetEventsResponse.ResponseMessages.GetEventsResponseMessage | fl 
  208.         Remove-Item ("c:\temp\" + $MailboxName + "PullSubWM.wm") 
  209.         Write-Host ("Removed old watermarkFile") 
  210.     } 
  211.     else{ 
  212.         if($rdResponseDocument.Envelope.Body.GetEventsResponse.ResponseMessages.GetEventsResponseMessage.ResponseClass -eq "Success"){ 
  213.             $pullSubSav = "" | Select Watermark,SubscriptionID 
  214.             $wmark2 = $rdResponseDocument.getElementsByTagName("t:Watermark")            
  215.             $pullSubSav.Watermark = $wmark2.Item(($wmark2.Count-1))."#text"  
  216.             $pullSubSav.SubscriptionID = $rdResponseDocument.Envelope.Body.GetEventsResponse.ResponseMessages.GetEventsResponseMessage.Notification.SubscriptionId  
  217.             $pullSubSav | Export-Csv -Path ("c:\temp\" + $MailboxName + "PullSubWM.wm") -NoTypeInformation 
  218.             Write-Host "Subscription saved" 
  219.             $movedEvents = $rdResponseDocument.getElementsByTagName("t:MovedEvent")  
  220.             if($movedEvents.Count -gt 0){ 
  221.                 try{ 
  222.                     for($intmc=0;$intmc -lt $movedEvents.Count;$intmc++){ 
  223.                         $item = [Microsoft.Exchange.WebServices.Data.Item]::Bind($service, $movedEvents.Item($intmc).ItemId.Id); 
  224.                         $ItemEvt = "" | Select EventTime,MessageDateTimeReceived,Subject,MovedFrom,MovedTo,LastModifiedName 
  225.                         $ItemEvt.EventTime = $movedEvents.Item($intmc).TimeStamp 
  226.                         Write-Host ("Processing : " + $item.Subject) 
  227.                         write-host ("Last Modified by :" + $item.LastModifiedName) 
  228.                         $ItemEvt.Subject = $item.Subject  
  229.                         $ItemEvt.LastModifiedName = $item.LastModifiedName 
  230.                         $ItemEvt.MessageDateTimeReceived = $item.DateTimeReceived        
  231.                                  
  232.                         #$movedEvents.Item($intmc).OldItemId.Id 
  233.                      
  234.                         if($FolderCollection.containsKey($movedEvents.Item($intmc).OldParentFolderId.Id)){ 
  235.                             Write-Host ("Moved From Folder " + $FolderCollection[$movedEvents.Item($intmc).OldParentFolderId.Id]) 
  236.                             $ItemEvt.MovedFrom = $FolderCollection[$movedEvents.Item($intmc).OldParentFolderId.Id] 
  237.                         }                    
  238.                         if($FolderCollection.containsKey($movedEvents.Item($intmc).ParentFolderId.Id)){ 
  239.                             Write-Host ("Moved To Folder " + $FolderCollection[$movedEvents.Item($intmc).ParentFolderId.Id]) 
  240.                             $ItemEvt.MovedTo = $FolderCollection[$movedEvents.Item($intmc).ParentFolderId.Id] 
  241.                         } 
  242.                         else{ 
  243.                              if ($movedEvents.Item($intmc).ParentFolderId.Id -eq $Deletions.Id.UniqueId) 
  244.                              { 
  245.                                 Write-Host ("Moved to Recoverable Items - Deleted Items");                           
  246.                              } 
  247.                              $ItemEvt.MovedTo = "Moved to Recoverable Items - Deleted Items" 
  248.                         } 
  249.                         Add-Content -Path $Script:changeLog ($ItemEvt.EventTime + ",`"" + $ItemEvt.MessageDateTimeReceived + "`",`"" + $ItemEvt.Subject + "`"," + $ItemEvt.MovedFrom + "," + $ItemEvt.MovedTo + "," + $ItemEvt.LastModifiedName) 
  250.                     } 
  251.                 } 
  252.                 catch{ 
  253.                     Write-Host ($Error | fl) 
  254.                     $Error.Clear() 
  255.                 } 
  256.             } 
  257.  
  258.         } 
  259.     }    
  260. } 
  261. else{ 
  262.     $InboxId = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)   
  263.     $fldArray = new-object Microsoft.Exchange.WebServices.Data.FolderId[] 1   
  264.     $fldArray[0] = $InboxId   
  265.     $pullsub = $service.SubscribeToPullNotifications($fldArray,$duration, $WaterMark, $evEvents); 
  266.     $gEvents =  $pullsub.GetEvents(); 
  267.     $pullSubSav = "" | Select Watermark,SubscriptionID 
  268.     $pullSubSav.Watermark = $pullsub.Watermark 
  269.     $pullSubSav.SubscriptionID = $pullsub.Id 
  270.     $pullSubSav | Export-Csv -Path ("c:\temp\" + $MailboxName + "PullSubWM.wm") -NoTypeInformation 
  271.     Write-Host "Subscription saved"  
  272. }  



Sunday, April 06, 2014

Export the GAL or Address list with EWS to Vcards in 2013 MEC sample 1

This is the first of the sample scripts I included in my MEC talk last Monday.  This script uses 3 of the new operations in EWS in Exchange 2013 to export all the address list entries of a particular Addresslist to individual Vcards (including the user photo) using just EWS. None of these operations are included in the EWS Managed API, so to use these operations I'm using some Raw SOAP and posting the EWS request using the standard httpwebrequest class in .Net and processing the response in XML.

The FindPeople Operation is used firstly to page through all the entries in an Address list in groups of 100 entries at a time. To use this operation you first need to get the GUID for the address list you want to export and hard code this in the $ABGUID variable eg.

$ABGUID = "6c118670-2f72-4213-944c-ab1e97d63f9b";

To get the GUID for your Global Address List you need to use the Get-GlobalAddressList Cmdlet http://technet.microsoft.com/en-us/library/aa996579%28v=exchg.150%29.aspx or if you want to export one of the other Address lists you can use the Get-AddressList cmdlet http://technet.microsoft.com/en-us/library/aa996782(v=exchg.150).aspx .

eg



To use these cmdlet's in Office365 you need to make sure you are assigned the Address Lists Role in RBAC

eg run

New-ManagementRoleAssignment -Role 'Address Lists' -User username

The FindPeople operation returns the PersonaId for each of the AddressList entries so the next part of the script uses the GetPersona Operation to get all the Address list entries details. Personas are a different way of representing contact data this is outlined in http://msdn.microsoft.com/en-us/library/office/jj190895(v=exchg.150).aspx . This particular script just uses this operation to grab the contact details stored in Active Directory (or Azure AD) for the particular persona (or Address list Entry in question). Currently I haven't worked out a way to batch this operation in EWS (if anyone knows please ping me) so its executing one OP per entry(which is kind of slow).
The last operation the script uses is the GerUserPhoto operation which is just a REST endpoint so it's nice and easy to use. It returns a ByteArray which then gets converted to a Base64 string with LineBreaks so its properly formatted in the VCard file.
To run this script you need to pass it in a Mailbox to run as and as I mentioned before you need to hardcode the GUID of the AddressList you want to export. While none of the operations used to do the export are in the Managed API, I've included in the usually Managed API Autodiscovery routines.  You can also set the location of the export directory in 
$exportFolder = "c:\temp\"
I've put a download of this script here the code itself looks like


  1. ## Get the Mailbox to Access from the 1st commandline argument  
  2.   
  3. $MailboxName = $args[0]  
  4. $exportFolder = "c:\temp\" 
  5.  
  6. $ABGUID = "6c118670-2f72-4213-944c-ab1e97d63f9b"; 
  7.  
  8. ## Load Managed API dll   
  9. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll"   
  10.    
  11. ## Set Exchange Version   
  12. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2   
  13.    
  14. ## Create Exchange Service Object   
  15. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)   
  16.    
  17. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials   
  18.    
  19. #Credentials Option 1 using UPN for the windows Account   
  20. $psCred = Get-Credential   
  21. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())   
  22. $service.Credentials = $creds       
  23.    
  24. #Credentials Option 2   
  25. #service.UseDefaultCredentials = $true   
  26.    
  27. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates   
  28.    
  29. ## Code From http://poshcode.org/624 
  30. ## Create a compilation environment 
  31. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider 
  32. $Compiler=$Provider.CreateCompiler() 
  33. $Params=New-Object System.CodeDom.Compiler.CompilerParameters 
  34. $Params.GenerateExecutable=$False 
  35. $Params.GenerateInMemory=$True 
  36. $Params.IncludeDebugInformation=$False 
  37. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null 
  38.  
  39. $TASource=@' 
  40.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  41.     public class TrustAll : System.Net.ICertificatePolicy { 
  42.       public TrustAll() {  
  43.       } 
  44.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  45.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  46.         System.Net.WebRequest req, int problem) { 
  47.         return true; 
  48.       } 
  49.     } 
  50.   } 
  51. '@  
  52. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource) 
  53. $TAAssembly=$TAResults.CompiledAssembly 
  54.  
  55. ## We now create an instance of the TrustAll and attach it to the ServicePointManager 
  56. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll") 
  57. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll 
  58.  
  59. ## end code from http://poshcode.org/624 
  60.    
  61. ## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use   
  62.    
  63. #CAS URL Option 1 Autodiscover   
  64. $service.AutodiscoverUrl($MailboxName,{$true})   
  65. "Using CAS Server : " + $Service.url    
  66.  
  67.  
  68.     
  69. #CAS URL Option 2 Hardcoded   
  70.    
  71. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"   
  72. #$service.Url = $uri     
  73.    
  74. ## Optional section for Exchange Impersonation   
  75.    
  76. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)  
  77.  
  78. function getPeopleRequest($offset){ 
  79.  
  80. $request = @"  
  81. <?xml version="1.0" encoding="utf-8"?>  
  82. <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">  
  83. <soap:Header>  
  84. <RequestServerVersion Version="Exchange2013" xmlns="http://schemas.microsoft.com/exchange/services/2006/types" />  
  85. </soap:Header><soap:Body>  
  86. <FindPeople xmlns="http://schemas.microsoft.com/exchange/services/2006/messages"><PersonaShape>  
  87. <BaseShape xmlns="http://schemas.microsoft.com/exchange/services/2006/types">Default</BaseShape>  
  88. </PersonaShape><IndexedPageItemView MaxEntriesReturned="100" Offset="$offset" BasePoint="Beginning" />  
  89. <ParentFolderId>  
  90. <AddressListId Id="$ABGUID" xmlns="http://schemas.microsoft.com/exchange/services/2006/types" />  
  91. </ParentFolderId></FindPeople></soap:Body></soap:Envelope>  
  92. "@ 
  93. return $request 
  94. } 
  95.  
  96. function getPersonaRequest($PersonalId){ 
  97. $request = @"  
  98. <?xml version="1.0" encoding="utf-8"?>  
  99. <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"  
  100.                xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">  
  101.   <soap:Header>  
  102.     <t:RequestServerVersion Version="Exchange2013"/>  
  103.   </soap:Header>  
  104.   <soap:Body xmlns="http://schemas.microsoft.com/exchange/services/2006/messages">  
  105.     <GetPersona>  
  106.       <PersonaId Id="$PersonalId"/>  
  107.     </GetPersona>  
  108.   </soap:Body>  
  109. </soap:Envelope>  
  110. "@ 
  111. return $request 
  112. } 
  113.  
  114. function AutoDiscoverPhotoURL{ 
  115.        param ( 
  116.               $EmailAddress="$( throw 'Email is a mandatory Parameter' )", 
  117.               $Credentials="$( throw 'Credentials is a mandatory Parameter' )" 
  118.               ) 
  119.        process{ 
  120.               $version= [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013 
  121.               $adService= New-Object Microsoft.Exchange.WebServices.Autodiscover.AutodiscoverService($version); 
  122.               $adService.Credentials = $Credentials 
  123.               $adService.EnableScpLookup=$false; 
  124.               $adService.RedirectionUrlValidationCallback= {$true} 
  125.               $adService.PreAuthenticate=$true; 
  126.               $UserSettings= new-object Microsoft.Exchange.WebServices.Autodiscover.UserSettingName[] 1 
  127.               $UserSettings[0] = [Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::ExternalPhotosUrl 
  128.               $adResponse=$adService.GetUserSettings($EmailAddress, $UserSettings) 
  129.               $PhotoURI= $adResponse.Settings[[Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::ExternalPhotosUrl] 
  130.               return $PhotoURI.ToString() 
  131.        } 
  132. } 
  133.  
  134. $Script:PhotoURL = AutoDiscoverPhotoURL -EmailAddress $MailboxName  -Credentials $creds 
  135. Write-host ("Photo URL : " + $Script:PhotoURL)  
  136.  
  137. function ProcessPersona($PersonalId){ 
  138.  
  139.  
  140.     $personalRequest = getPersonaRequest ($PersonalId) 
  141.     $mbMailboxFolderURI = New-Object System.Uri($service.url)   
  142.     $wrWebRequest = [System.Net.WebRequest]::Create($mbMailboxFolderURI)   
  143.     $wrWebRequest.CookieContainer =  New-Object System.Net.CookieContainer    
  144.     $wrWebRequest.KeepAlive = $false;   
  145.     $wrWebRequest.Headers.Set("Pragma", "no-cache");   
  146.     $wrWebRequest.Headers.Set("Translate", "f");   
  147.     $wrWebRequest.Headers.Set("Depth", "0");   
  148.     $wrWebRequest.ContentType = "text/xml";   
  149.     $wrWebRequest.ContentLength = $expRequest.Length;   
  150.     $wrWebRequest.Timeout = 90000;   
  151.     $wrWebRequest.Method = "POST";   
  152.     $wrWebRequest.Credentials = $creds   
  153.     $wrWebRequest.UserAgent = "EWS Script" 
  154.      
  155.     $bqByteQuery = [System.Text.Encoding]::ASCII.GetBytes($personalRequest);   
  156.     $wrWebRequest.ContentLength = $bqByteQuery.Length;   
  157.     $rsRequestStream = $wrWebRequest.GetRequestStream();   
  158.     $rsRequestStream.Write($bqByteQuery, 0, $bqByteQuery.Length);   
  159.     $rsRequestStream.Close();   
  160.     $wrWebResponse = $wrWebRequest.GetResponse();   
  161.     $rsResponseStream = $wrWebResponse.GetResponseStream()   
  162.     $sr = new-object System.IO.StreamReader($rsResponseStream);   
  163.     $rdResponseDocument = New-Object System.Xml.XmlDocument   
  164.     $rdResponseDocument.LoadXml($sr.ReadToEnd());   
  165.     $Persona =@($rdResponseDocument.getElementsByTagName("Persona"))  
  166.     $Persona 
  167.     $DisplayName = ""; 
  168.     if($Persona.DisplayName -ne $null){ 
  169.         $DisplayName = $Persona.DisplayName."#text"  
  170.     }   
  171.     $fileName =  $exportFolder + $DisplayName + "-" + [Guid]::NewGuid().ToString() + ".vcf" 
  172.     add-content -path $filename "BEGIN:VCARD" 
  173.     add-content -path $filename "VERSION:2.1" 
  174.     $givenName = "" 
  175.     if($Persona.GivenName -ne $null){ 
  176.         $givenName = $Persona.GivenName."#text"  
  177.     }  
  178.     $surname = "" 
  179.     if($Persona.Surname -ne $null){ 
  180.         $surname = $Persona.Surname."#text"  
  181.     }  
  182.     add-content -path $filename ("N:" + $surname + ";" + $givenName) 
  183.     add-content -path $filename ("FN:" + $Persona.DisplayName."#text")  
  184.     $Department = ""; 
  185.     if($Persona.Department -ne $null){ 
  186.         $Department = $Persona.Department."#text"  
  187.     }  
  188.     if($Persona.EmailAddress -ne $null){  
  189.         add-content -path $filename ("EMAIL;PREF;INTERNET:" + $Persona.EmailAddress.EmailAddress) 
  190.     } 
  191.     $CompanyName = ""; 
  192.     if($Persona.CompanyName -ne $null){ 
  193.         $CompanyName = $Persona.CompanyName."#text"  
  194.     }  
  195.     add-content -path $filename ("ORG:" + $CompanyName + ";" + $Department)  
  196.     if($Persona.Titles -ne $null){ 
  197.         add-content -path $filename ("TITLE:" + $Persona.Titles.StringAttributedValue.Value) 
  198.     } 
  199.     if($Persona.MobilePhones -ne $null){ 
  200.         add-content -path $filename ("TEL;CELL;VOICE:" + $Persona.MobilePhones.PhoneNumberAttributedValue.Value.Number)      
  201.     } 
  202.     if($Persona.HomePhones -ne $null){ 
  203.         add-content -path $filename ("TEL;HOME;VOICE:" + $Persona.HomePhones.PhoneNumberAttributedValue.Value.Number)        
  204.     } 
  205.     if($Persona.BusinessPhoneNumbers -ne $null){ 
  206.         add-content -path $filename ("TEL;WORK;VOICE:" + $Persona.BusinessPhoneNumbers.PhoneNumberAttributedValue.Value.Number)      
  207.     } 
  208.     if($Persona.WorkFaxes -ne $null){ 
  209.         add-content -path $filename ("TEL;WORK;FAX:" + $Persona.WorkFaxes.PhoneNumberAttributedValue.Value.Number) 
  210.     } 
  211.     if($Persona.BusinessHomePages -ne $null){ 
  212.         add-content -path $filename ("URL;WORK:" + $Persona.BusinessHomePages.StringAttributedValue.Value) 
  213.     } 
  214.     if($Persona.BusinessAddresses -ne $null){ 
  215.         $Country = $Persona.BusinessAddresses.PostalAddressAttributedValue.Value.Country 
  216.         $City = $Persona.BusinessAddresses.PostalAddressAttributedValue.Value.City 
  217.         $Street = $Persona.BusinessAddresses.PostalAddressAttributedValue.Value.Street 
  218.         $State = $Persona.BusinessAddresses.PostalAddressAttributedValue.Value.State 
  219.         $PCode = $Persona.BusinessAddresses.PostalAddressAttributedValue.Value.PostalCode 
  220.         $addr =  "ADR;WORK;PREF:;" + $Country + ";" + $Street + ";" +$City + ";" + $State + ";" + $PCode + ";" + $Country 
  221.         add-content -path $filename $addr 
  222.     } 
  223.     try{ 
  224.         $PhotoSize = "HR96x96 
  225.         $PhotoURL= $Script:PhotoURL + "/GetUserPhoto?email="  + $Persona.EmailAddress.EmailAddress + "&size=" + $PhotoSize; 
  226.         $wbClient = new-object System.Net.WebClient 
  227.         $wbClient.Credentials = $creds 
  228.         $photoBytes = $wbClient.DownloadData($PhotoURL); 
  229.         add-content -path $filename "PHOTO;ENCODING=BASE64;TYPE=JPEG:" 
  230.         $ImageString = [System.Convert]::ToBase64String($photoBytes,[System.Base64FormattingOptions]::InsertLineBreaks) 
  231.         add-content -path $filename $ImageString 
  232.         add-content -path $filename "`r`n"   
  233.     } 
  234.     catch{ 
  235.  
  236.     } 
  237.     add-content -path $filename "END:VCARD" 
  238.  
  239. } 
  240.  
  241. $peopleCollection = @() 
  242. $offset = 0; 
  243.  
  244. do{ 
  245.     $mbMailboxFolderURI = New-Object System.Uri($service.url)   
  246.     $wrWebRequest = [System.Net.WebRequest]::Create($mbMailboxFolderURI)   
  247.     $wrWebRequest.CookieContainer =  New-Object System.Net.CookieContainer    
  248.     $wrWebRequest.KeepAlive = $false;   
  249.     $wrWebRequest.Useragent = "EWS Script" 
  250.     $wrWebRequest.Headers.Set("Pragma", "no-cache");   
  251.     $wrWebRequest.Headers.Set("Translate", "f");   
  252.     $wrWebRequest.Headers.Set("Depth", "0");   
  253.     $wrWebRequest.ContentType = "text/xml";   
  254.     $wrWebRequest.ContentLength = $expRequest.Length;   
  255.     $wrWebRequest.Timeout = 60000;   
  256.     $wrWebRequest.Method = "POST";   
  257.     $wrWebRequest.Credentials = $creds   
  258.  
  259.     $fpRequest = getPeopleRequest ($offset) 
  260.     $bqByteQuery = [System.Text.Encoding]::ASCII.GetBytes($fpRequest);   
  261.     $wrWebRequest.ContentLength = $bqByteQuery.Length;   
  262.     $rsRequestStream = $wrWebRequest.GetRequestStream();   
  263.     $rsRequestStream.Write($bqByteQuery, 0, $bqByteQuery.Length);   
  264.     $rsRequestStream.Close();   
  265.     $wrWebResponse = $wrWebRequest.GetResponse();   
  266.     $rsResponseStream = $wrWebResponse.GetResponseStream()   
  267.     $sr = new-object System.IO.StreamReader($rsResponseStream);   
  268.     $rdResponseDocument = New-Object System.Xml.XmlDocument   
  269.     $rdResponseDocument.LoadXml($sr.ReadToEnd());   
  270.     $totalCount = @($rdResponseDocument.getElementsByTagName("TotalNumberOfPeopleInView"))  
  271.     $Personas =@($rdResponseDocument.getElementsByTagName("Persona"))   
  272.     Write-Host ("People Count : " + $Personas.Count) 
  273.     $offset += $Personas.Count 
  274.     foreach($persona in $Personas){ 
  275.         if($persona.PersonaType -eq "Person"){ 
  276.             ProcessPersona($persona.PersonaId.Id.ToString())  
  277.         } 
  278.     } 
  279.     [Int32]$tc = $totalCount."#text"  
  280.     Write-Host ("Offset: " + $offset) 
  281.     Write-Host ("Total count: " + $tc)  
  282.   
  283. }while($tc -gt $offset)