Requirement: Would like to have basic auth setup using custom php script.
Would like to :
Create user credentials dynamically
Create the cred php file with these credentials updated
Update the username & password to respective for Azure WebApp settings.
[ Note: FTPing the cred and auth files automatically if missing would be in upcoming post ]
Tools & Pre-requisites :
Azure Powershell 4.0+
Windows Powershell ISE
Knowledge of Storage Account ( Name, RG's )
Knowledge of WebApp for which backup is desired ( WebApp Name and RG )
Valid & Active Azure portal / AD login credentials
RESULT : On Azure Portal, The webapp in focus would have 'global_cred' variable key set with credentials generated stored as its value in application settings section as [username:password] format.
#######################
# Function to Generate dynamic 22char random string for password use
# Call custom function to write php constants for basic auth
Function writeFocusDomainCredFile{
param( $configObject, $hash, $farmprojectname )
#setting auth username:password
$configObject.authPasswordKey = (Get-RandomAlphanumericString -length 22 | Tee-Object -variable teeTime )
$hash['global_cred'] = [String]( $configObject.authUsernameKey + ':' + $configObject.authPasswordKey )
$prfilename = ( $configObject.ftpappdirectory + '\cred.php')
writeProjectBasicAuthCredOnNew -filename $prfilename -configObject $configObject
}
##########################
# Function to write the credentials to cred php file for basic auth use
# This file with other dependent files could be automatically ftp'd.
# Would share in another post
Function writeProjectBasicAuthCredOnNew{
param( $filename
,$configObject
)
writeDeployFileFiltersForDomain -ReportFileName $filename
Add-Content $filename ( "<?php" )
Add-Content $filename ("define('WPIZED_AUTH_USER', '" + $configObject.authUsernameKey + "');");
Add-Content $filename ("define('WPIZED_AUTH_PASS', '" + $configObject.authPasswordKey + "');");
}
###################################################################################################
# Configure Below as You prefer or desire #
$properties = #{
'ResourceName' = "AzureAppName";
'myResourceGroupName' = "{App Resource Group Name}";
'mySubscriptionName' = "{subscription name}";
'adminEmail' = "H.Bala#volunteering.com";
'ResourceGroupLocation' = "East US";
'authUsernameKey' = 'HBalaUsername'; #For this post, using fixed username as 'HBalaUsername'
'authPasswordKey' = '';
'PathFormatDate' = Get-Date -UFormat "%Y_%m_%d";
}
$configObject = New-Object –TypeName PSObject –Prop $properties
Write-Output $configObject
#Login cmdlet for active session
Login-AzureRmAccount
Get-AzureRmSubscription –SubscriptionName $configObject.mySubscriptionName | Select-AzureRmSubscription
(Get-AzureRmContext).Subscription
Select-AzureRMSubscription -SubscriptionName $configObject.mySubscriptionName
#Pull the Webapp details and configuration
$webApp = Get-AzureRMWebApp -ResourceGroupName $configObject.myResourceGroupName -Name $configObject.ResourceName
#Pull the Application Listing Environment / Configuration Variables
$appSettingList = $webApp.SiteConfig.AppSettings
$hash = #{}
ForEach ($kvp in $appSettingList) {
$hash[$kvp.Name] = $kvp.Value
}
writeFocusDomainCredFile -configObject $configObject -hash $hash
#[FTP Deploy Logic of this file and other basic auth or files shall cover in seperate topic ]
#[ Only setting the generated Credentials and saving to Application setting focused here ]
Set-AzureRMWebApp -ResourceGroupName $configObject.myResourceGroupName -Name $configObject.ResourceName -AppSettings $hash
Disclaimer: The intention is to share to another newbie who might find this helpful.
Related
I have a powershell script running in an azure function app that grabs a json list of IPs, parses it and just returns a list of IPs with 1 per line. I need to take this output and save it to a specific azure storage account. I'm attempting to use the static website in the storage account to create an HTTP endpoint for others to retrieve the list of IPs.
Here's the script
# Input bindings are passed in via param block.
param($Timer)
# Get the current universal time in the default string format.
$currentUTCtime = (Get-Date).ToUniversalTime()
# The 'IsPastDue' property is 'true' when the current function invocation is later than scheduled.
if ($Timer.IsPastDue) {
Write-Host "PowerShell timer is running late!"
}
# Write an information log with the current time.
Write-Host "PowerShell timer trigger function ran! TIME: $currentUTCtime"
#-------------------------------------------------------------------------------------------
$url = "https://ip-ranges.atlassian.com/"
Invoke-RestMethod $url
$iplist = Invoke-RestMethod $url
$iplist.items | select-object -ExpandProperty cidr
out-file $outputBlob
I've tested the function in azure and it runs there just fine. I can't seem to get the integration outputs section of the function app to work. The settings for the outputs is
Binding type - azure blob storage
blob paramter name - outputBlob
Path - test/list.txt
storage account connection - searched and selected the storage account
I am not finding much documentation on how to make my powershell script output to this storage account. The out-file clearly doesn't work.
----------- updated code ----------
Here is the code that now successfully saves the file to a container, but I still cant save to the $web container for the static website. The $ is not something I can use in the output binding
# Input bindings are passed in via param block.
param($Timer)
# Get the current universal time in the default string format.
$currentUTCtime = (Get-Date).ToUniversalTime()
# The 'IsPastDue' property is 'true' when the current function invocation is later than scheduled.
if ($Timer.IsPastDue) {
Write-Host "PowerShell timer is running late!"
}
# Write an information log with the current time.
Write-Host "PowerShell timer trigger function ran! TIME: $currentUTCtime"
#------------------------
$url = "https://ip-ranges.atlassian.com/"
Invoke-RestMethod $url
$call = Invoke-RestMethod $url
$iplist = $call.items | select-object -ExpandProperty cidr
$output = out-string -InputObject $iplist
Push-OutputBinding -Name outputBlob -Value $output
the outputBlob binding is configured under integration > outputs > and the Path which is in the format of container/file. I cannot specify $web/file.txt...but if I do web/file.txt it will create a web container and put the output as file.txt within it. I need to do this, but it must go in the $web container.
This is something that I've been meaning to try for a little while but not actually got around to. Decided to give it a shot today when I seen your question.
So it is possible to push content using the blob output binding but the functionality is limited.
run.ps1
using namespace System.Net
# Input bindings are passed in via param block.
param (
$Request,
$TriggerMetadata
)
# Call the atlassian API to get the address ranges
$atlassianUri = "https://ip-ranges.atlassian.com/"
$iplist = Invoke-RestMethod $atlassianUri -ErrorAction Stop
$cidrList = $iplist.items | select-object -ExpandProperty cidr
# Push the contents to blob storage using the outputBinding
Push-OutputBinding -Name myOutputBlob -Value $cidrList
# Return a simple response so I know it worked
Push-OutputBinding -Name Response -Value ([HttpResponseContext]#{
StatusCode = [HttpStatusCode]::OK
Body = 'Successfully Updated Blob Storage'
})
function.json
You would have to include an timer Input Binding in your function but I used HTTP so that I could trigger it on-demand to test that it would work.
I have provided a static path to the blob output binding. The Path property can not be dynamically assigned from within the function yet according to this open GitHub issue.
{
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "Request",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "Response"
},
{
"name": "myOutputBlob",
"type": "blob",
"path": "functioncopy/ipRanges.txt",
"connection": "MyStorageConnectionAppSetting",
"direction": "out"
}
]
}
The above code works however when sending the data to the blob file in the storage account Push-OutputBinding serialises the content to a JSON array e.g.
This may or may not work for you in it's current guise but I don't think there is a way using the output binding to just have a raw list.
You could however use the Az.Storage module within your function, create the file within your function execution and upload it that way instead
run.ps1
# Variables required - Fill these out
$storageAccountName = '<Insert Storage Account Here'
$containerName = '<Insert StorageContainer Name Here>'
$resourceGroupName = '<Insert resourceGroup Name Here>'
$subscriptionId = '<Insert subscriptionId Here>'
# Call the atlassian API to get the address ranges
$atlassianUri = "https://ip-ranges.atlassian.com/"
$iplist = Invoke-RestMethod $atlassianUri -ErrorAction Stop
$cidrList = $iplist.items | select-object -ExpandProperty cidr
# New-TemporaryFile uses [System.IO.Path]::GetTempPath() location
$tempFile = New-TemporaryFile
# Set the context to the subscription you want to use
# If your functionApp has access to more than one subscription it will load the first subscription by default.
# Possibly a good habit to be explicit about context.
Set-AzContext -Subscription $subscriptionId
# Get the Storage Account Key to authenticate
$storAccKeys = Get-AzStorageAccountKey -ResourceGroupName $resourceGroupName -Name $storageAccountName
$primaryKey = $storAccKeys | Where-Object keyname -eq 'key1' | Select-Object -ExpandProperty value
# Write the CIDR list to the temp file created earlier
$cidrList | Out-File $tempFile
# Create a Storage Context which will be used in the subsequent commands
$storageContext = New-AzStorageContext -StorageAccountName $storageAccountName -StorageAccountKey $primaryKey
# Upload the temp file to blob storage
$setAzStorageBlobContentSplat = #{
Container = $containerName
File = $tempFile.FullName
Blob = 'ipranges.txt'
Context = $storageContext
Properties = #{
ContentType = 'text/plain'
}
}
Set-AzStorageBlobContent #setAzStorageBlobContentSplat
# Return a simple response so I know it worked
Push-OutputBinding -Name Response -Value (
[HttpResponseContext]#{
StatusCode = [HttpStatusCode]::OK
Body = 'Successfully Updated Blob Storage'
}
)
You can see the documentation on Set-AzStorageBlobContent for examples on that here:
https://learn.microsoft.com/en-us/powershell/module/az.storage/set-azstorageblobcontent?view=azps-6.2.1#examples
I have an Azure Devops Process which uses a Service Principle to deploy a Power BI report from a .pbix file and updates the data source to point at the production Azure SQL server.
The outcome is that the report and dataset are deployed and the connection is updated, however the CREDENTIALS are blank (Username and Password), so in order to make it usable, someone has to log on to Power BI service, open the Dataset and update the credentials, which means there is a manual step involved in our CI/CD process.
I need help with updating the source credentials for the new data source via code so that this manual process is not needed
Any suggestion will be of great help. Thank You.
Here is a sample PowerShell script, using Microsoft Power BI Cmdlets, which will patch the credentials of the dataset and commented at the end are few rows, which will refresh the dataset after that (uncomment them if needed). Just replace the x-es at the top with the actual values (workspace and dataset name, application ID, etc.).
<#
Patch the credentials of a published report/dataset, so it can be refreshed.
#>
# Fill these ###################################################
$tenantId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # Get from Azure AD -> Properties (https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/Properties)
$applictionId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # Get Application (client) ID from Azure AD -> App registrations (https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps)
$applicationSecret = "xxxxxxxxxxxxxxxx" # Create it from application's "Certificates & secrets" section
$workspaceName = "xxxxxxxx"
$reportName = "xxxxxxxx" # Actually it is dataset name
$sqlUserName = "xxxxxxxx"
$sqlUserPassword = "xxxxxxxxxx"
################################################################
Import-Module MicrosoftPowerBIMgmt
$SecuredApplicationSecret = ConvertTo-SecureString -String $applicationSecret -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($applictionId, $SecuredApplicationSecret)
$sp = Connect-PowerBIServiceAccount -ServicePrincipal -Tenant $tenantId -Credential $credential
$workspace = Get-PowerBIWorkspace -Name $workspaceName
$dataset = Get-PowerBIDataset -WorkspaceId $workspace.Id -Name $reportName
$workspaceId = $workspace.Id
$datasetId = $dataset.Id
$datasources = Get-PowerBIDatasource -WorkspaceId $workspaceId -DatasetId $datasetId
foreach($datasource in $datasources) {
$gatewayId = $datasource.gatewayId
$datasourceId = $datasource.datasourceId
$datasourePatchUrl = "gateways/$gatewayId/datasources/$datasourceId"
Write-Host "Patching credentials for $datasourceId"
# HTTP request body to patch datasource credentials
$userNameJson = "{""name"":""username"",""value"":""$sqlUserName""}"
$passwordJson = "{""name"":""password"",""value"":""$sqlUserPassword""}"
$patchBody = #{
"credentialDetails" = #{
"credentials" = "{""credentialData"":[ $userNameJson, $passwordJson ]}"
"credentialType" = "Basic"
"encryptedConnection" = "NotEncrypted"
"encryptionAlgorithm" = "None"
"privacyLevel" = "Organizational"
}
}
# Convert body contents to JSON
$patchBodyJson = ConvertTo-Json -InputObject $patchBody -Depth 6 -Compress
# Execute PATCH operation to set datasource credentials
Invoke-PowerBIRestMethod -Method Patch -Url $datasourePatchUrl -Body $patchBodyJson
}
#$datasetRefreshUrl = "groups/$workspaceId/datasets/$datasetId/refreshes"
#Write-Host "Refreshing..."
#Invoke-PowerBIRestMethod -Method Post -Url $datasetRefreshUrl
I am trying to create an azure function that has to create azure dynamic group when i execute the function from MS flow. I am using below code for this purpose.
$groupName = $Request.Query.Name
$groupDesc = $Request.Query.Desc
$domainnames = $Request.Query.DomainName
$dynamicrule = ""
Foreach($domainname in $domainnames.Split(";"))
{
$dynamicrule = $dynamicrule + "(user.userPrincipalName -contains ""_$domainname"") or";
}
$dynamicrule = $dynamicrule -replace ".{2}$"
$dynamicrule = $dynamicrule + "and (user.objectId -ne null)";
New-AzureADMSGroup -DisplayName $groupName -Description $groupDesc -MailEnabled $False -MailNickName "group" -SecurityEnabled $True -GroupTypes "DynamicMembership" -MembershipRule $dynamicrule -MembershipRuleProcessingState "On"
When i execute the above command, i am getting below error messgae.
ERROR: The term 'New-AzureADMSGroup' is not recognized as the name of a cmdlet, function, script file, or operable program.Check the spelling of the name, or if a path was included, verify that the path is correct and try again.Exception :Type : System.Management.Automation.CommandNotFoundExceptionErrorRecord
Can sombody please help me on how can i create dynamic groups using azure function app.
Thanks,
Venu
From the error message, you did not install AzureAD powershell module in your function app. And if you want to create a dynamic group, you need to use the -MembershipRule parameter, it is just available in the preview version i.e. AzureADPreview module. Though the doc looks like the parameter is available in AzureAD, but per my test, it is not available.
Actually it is easy to solve the issue, but if you want to create a dynamic group with New-AzureADMSGroup, there will be a few follow-up issues, you could follow the steps below.
1.Navigate to the function app in the portal -> Identity -> enable the system-assigned identity(MSI) for your app.
2.Navigate to App files -> host.json -> make sure the managedDependency is Enabled.
{
"version": "2.0",
"managedDependency": {
"Enabled": true
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[1.*, 2.0.0)"
}
}
In the requirements.psd1, add the AzureADPreview like below, then it will install the AzureADPreview module for you automatically.
#{
'Az' = '5.*'
'AzureADPreview' = '2.0.2.129'
}
In the profile.ps1, remove all the things and add the lines below, this is used to solve the issue related to AzureAD powershell in function, without it, you will get an error, details here.
$64bitPowerShellPath = Get-ChildItem -Path $Env:Windir\WinSxS -Filter PowerShell.exe -Recurse -ErrorAction SilentlyContinue | Where-Object {$_.FullName -match "amd64"}
$env:64bitPowerShellPath=$64bitPowerShellPath.VersionInfo.FileName
3.If you want to use New-AzureADMSGroup to create group in Azure AD, you need the permission in Microsoft Graph, in this case, we use MSI to auth, so use the commands below to give the permission to your MSI.
Run the commands below in local with the Global admin user account, replace <functionapp-name>:
Connect-AzureAD
$MSI = (Get-AzureADServicePrincipal -Filter "displayName eq '<functionapp-name>'")
$MSGraphAppId = "00000003-0000-0000-c000-000000000000"
$GraphServicePrincipal = Get-AzureADServicePrincipal -Filter "appId eq '$MSGraphAppId'"
$PermissionName = "Group.ReadWrite.All"
$AppRole = $GraphServicePrincipal.AppRoles | Where-Object {$_.Value -eq $PermissionName -and $_.AllowedMemberTypes -contains "Application"}
New-AzureADServiceAppRoleAssignment -ObjectId $MSI.ObjectId -PrincipalId $MSI.ObjectId -ResourceId $GraphServicePrincipal.ObjectId -Id $AppRole.Id
4.After step 2, navigate to the kudu(in the Advanced Tools blade of the function app) -> data -> ManagedDependencies -> click the file with the format like 201208083153165.r(choose the newest one via the Modified time) -> check if the AzureADPreview module was installed successfully like below.
5.After the module was installed, in your function code, use the lines below, in my sample, I use this sample to test directly, you could change the code depends on your requirements, remember to replace 201208083153165.r with yours in step 4, it works fine on my side.
using namespace System.Net
# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)
# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request."
# Interact with query parameters or the body of the request.
$name = $Request.Query.Name
if (-not $name) {
$name = $Request.Body.Name
}
$body = "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
if ($name) {
$body = "Hello, $name. This HTTP triggered function executed successfully."
}
$script = {
if ($env:MSI_SECRET) {
Disable-AzContextAutosave -Scope Process | Out-Null
Connect-AzAccount -Identity
}
$context = Get-AzContext
$graphtoken = (Get-AzAccessToken -ResourceUrl "https://graph.microsoft.com").Token
$aadtoken = (Get-AzAccessToken -ResourceUrl "https://graph.windows.net").Token
Import-Module D:\home\data\ManagedDependencies\201208083153165.r\AzureADPreview
Connect-AzureAD -AccountId $context.Account -TenantId $context.Tenant -MsAccessToken $graphtoken -AadAccessToken $aadtoken
New-AzureADMSGroup -DisplayName "joyd1" -Description "Dynamic group created from PS" -MailEnabled $False -MailNickName "group" -SecurityEnabled $True -GroupTypes "DynamicMembership" -MembershipRule "(user.department -contains ""Marketing"")" -MembershipRuleProcessingState "On"
}
&$env:64bitPowerShellPath -WindowStyle Hidden -NonInteractive -Command $Script
# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]#{
StatusCode = [HttpStatusCode]::OK
Body = $body
})
Check the group in the portal:
Is it possible to use the Connect-SPOService cmdlet with an application identifier & secret? I need to get information about site collections within an azure function that are only available through the get-sposite cmdlet.
I'm trying to set up an Azure Function that uses the SharePoint Online PowerShell module to report all site collections that have external sharing enabled.
As I don't want to include my personal credentials in this Azure Function I set up an application identifier in Azure AD.
I am able to use this app id with the PnP Cmdlets (connect-pnponline -appid ...) but the pnp command get-pnpsite do not return the needed detail information.
Below is the code with pnp framework, where all Sharing* properties are empty.
Connect-PnPOnline -AppId $appid -AppSecret $appsecret -Url $adminUrl
$content = #()
Get-PnPTenantSite -Filter "Url -notlike ""*/personal*""" | ? {$_.SharingCapability -ne "Disabled" } | % {
$connection = Connect-PnPOnline -ReturnConnection -Url $_.url -AppId $AppId -AppSecret $AppSecret
$site = Get-PnPSite -Connection $connection;
$content += #{
title= $site.Title;
url=$site.Url;
owner=$site.Owner;
SharingCapability=$site.SharingCapability;
SharingDomainRestrictionMode=$site.SharingDomainRestrictionMode;
SharingAllowedDomainList=$site.SharingAllowedDomainList;
SharingBlockedDomainList=$site.SharingBlockedDomainList}
}
This Code works, but needs actural user credentials:
param (
# Parameter help description
[Parameter(Mandatory=$true)]
[string]$TenantName,
# Parameter help description
[Parameter(Mandatory=$true)]
[string]$DestinationPath
)
$dateStr = Get-Date -Format yyyy-MM-dd_HH-mm-ss
$filename = "ExternalSharingReport_$dateStr.csv"
$content = #()
$adminUrl = "https://$TenantName-admin.sharepoint.com"
Connect-SPOService -Url $adminUrl
$content += "Title; Url; Owner; SharingCapability; SharingDomainRestrictionMode; SharingAllowedDomainList; SharingBlockedDomainList"
Get-SpoSite | ? {$_.Url -notlike "*/personal*" -AND $_.SharingCapability -ne "Disabled" } | % {
$site = Get-SPOSite $_.url;
$content += "$($site.Title); $($site.Url); $($site.Owner); $($site.SharingCapability); $($site.SharingDomainRestrictionMode); $($site.SharingAllowedDomainList); $($site.SharingBlockedDomainList)"
}
$completPath = Join-Path -Path $DestinationPath -ChildPath $filename
$content > $completPath
I would expect to be able to use the default cmdlet like this:
Connect-SPOService $adminUrl -AppId $appId -AppSecret $appSecret
You have to grant permission to the app.
Either at site collection level or at tenant level.
Grant permission at site collection level :
Open https://yourtenant.sharepoint.com/sites/yoursite/_layouts/15/appinv.aspx
Paste your cilent ID in the first field and click Lookup. It should autopopulate the otherfields
Paste the app permission request in the later field. It may vary depending on the permission you want to give. Do not forget to grant AppOnly authentication. Ex: full trust on the site collection :
<AppPermissionRequests AllowAppOnlyPolicy="true">
<AppPermissionRequest Scope="http://sharepoint/content/sitecollection" Right="FullControl" />
</AppPermissionRequests>
Grant to tenant level
Same as above, but using https://yourtenant-admin.sharepoint.com/_layouts/15/appinv.aspx
Full control in the whole tenant request is :
<AppPermissionRequests AllowAppOnlyPolicy="true">
<AppPermissionRequest Scope="http://sharepoint/content/tenant" Right="FullControl" />
</AppPermissionRequests>
I am currently trying to make this script run on exchange 2013 to convert folder types from IPF.IMAP to IPF.NOTE as the folders are not showing on mobile devices after being imported from Imap. This script returns 0 results after running and multiple Doesnt Exist. If I output the folder names they are coming through, so i am not sure why the FindFolders is not returning any results.
I tried turning on impersonation (commented out here) but get an error saying I do not have permissions to impersonate even though I am logged in as administrator and running on powershell as admin. I am not sure if this is even necessary as the script works fine and returns the folder names for both $mbxfolder.Name and $SfSearchFilter, but only until it hits the FindFolders line, then the TotalCount is always 0.
Import-Module -Name "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"
$exchService = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService
$exchService.UseDefaultCredentials = $true
$exchService.AutodiscoverUrl('email#domain.com', {$true})
$MBXID = "email#domain.com" #Define mailboxID
foreach ($MailboxIdentity in $MBXID) {
Write-Host "Searching for $MailboxIdentity"
$mailbox = (Get-Mailbox -Identity $MailboxIdentity)
$MailboxName = (Get-Mailbox -Identity $MailboxIdentity).PrimarySmtpAddress.ToString()
$MailboxRootid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root,$MailboxName) #MsgFolderRoot selection and creation of new root
$MailboxRoot = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($exchService,$MailboxRootid)
#$exchService.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId -ArgumentList ([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress),$MailboxName #Define impersonation
$folderid = $MailboxRootid
$f1 = $MailboxRoot
$fold = get-mailboxfolderstatistics $MailboxIdentity #Getting complete list of selected mailbox
foreach ($mbxfolder in $fold){
#Define Folder View Really only want to return one object
$fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(100) #page size for displayed folders
$fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep; #Search traversal selection Deep = recursively
#Define a Search folder that is going to do a search based on the DisplayName of the folder
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::Displayname,$MBXFolder.name) #for each folder in mailbox define search
$findFolderResults = $MailboxRoot.FindFolders($SfSearchFilter,$fvFolderView) #for each folder in mailbox define folder view (this is online task for store.exe) and perform search
if ($findFolderResults.TotalCount -eq 0){ "Folder Doesn't Exist" } #Info if folder still exist
else {"Folder Exist"
ForEach ($Folder in $findFolderResults.Folders) { #for each folder in folder results perform check of folder class
$folder.folderclass #Info about folder class
if ($Folder.folderclass -eq "IPF.Imap"){ #If folder class is target type, do change and update
$Folder.folderclass = "IPF.Note" #Folder class change in variable
Write-Host "Updating folder $folder.name to correct type IPF.Note. Folder will start to be visible in OWA"
$Folder.update() #Folder class update in mailbox via EWS
}
}
}
}
}
It doesn't really make much sense to enumerate the the folders using Get-MailboxFolderStatistics and then search for each folder in EWS. This is going to really slow and unnecessary (you have the folderId anyway from Get-MailboxFolderStatistics so you can just convert that and bind to it). However I would
Get rid of Get-MailboxFolderStatistics altogether and just use straight EWS to enumerate the Folders in the Mailbox and do your fixes as this will be much quicker eg
## Get the Mailbox to Access from the 1st commandline argument
$MailboxName = $args[0]
## Load Managed API dll
###CHECK FOR EWS MANAGED API, IF PRESENT IMPORT THE HIGHEST VERSION EWS DLL, ELSE EXIT
$EWSDLL = (($(Get-ItemProperty -ErrorAction SilentlyContinue -Path Registry::$(Get-ChildItem -ErrorAction SilentlyContinue -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Exchange\Web Services'|Sort-Object Name -Descending| Select-Object -First 1 -ExpandProperty Name)).'Install Directory') + "Microsoft.Exchange.WebServices.dll")
if (Test-Path $EWSDLL)
{
Import-Module $EWSDLL
}
else
{
"$(get-date -format yyyyMMddHHmmss):"
"This script requires the EWS Managed API 1.2 or later."
"Please download and install the current version of the EWS Managed API from"
"http://go.microsoft.com/fwlink/?LinkId=255472"
""
"Exiting Script."
exit
}
## Set Exchange Version
$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1
## Create Exchange Service Object
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)
## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials
#Credentials Option 1 using UPN for the windows Account
$psCred = Get-Credential
$creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())
$service.Credentials = $creds
#$service.TraceEnabled = $true
#Credentials Option 2
#service.UseDefaultCredentials = $true
## Choose to ignore any SSL Warning issues caused by Self Signed Certificates
## Code From http://poshcode.org/624
## Create a compilation environment
$Provider=New-Object Microsoft.CSharp.CSharpCodeProvider
$Compiler=$Provider.CreateCompiler()
$Params=New-Object System.CodeDom.Compiler.CompilerParameters
$Params.GenerateExecutable=$False
$Params.GenerateInMemory=$True
$Params.IncludeDebugInformation=$False
$Params.ReferencedAssemblies.Add("System.DLL") | Out-Null
$TASource=#'
namespace Local.ToolkitExtensions.Net.CertificatePolicy{
public class TrustAll : System.Net.ICertificatePolicy {
public TrustAll() {
}
public bool CheckValidationResult(System.Net.ServicePoint sp,
System.Security.Cryptography.X509Certificates.X509Certificate cert,
System.Net.WebRequest req, int problem) {
return true;
}
}
}
'#
$TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)
$TAAssembly=$TAResults.CompiledAssembly
## We now create an instance of the TrustAll and attach it to the ServicePointManager
$TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")
[System.Net.ServicePointManager]::CertificatePolicy=$TrustAll
## end code from http://poshcode.org/624
## 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
#CAS URL Option 1 Autodiscover
$service.AutodiscoverUrl($MailboxName,{$true})
"Using CAS Server : " + $Service.url
#CAS URL Option 2 Hardcoded
#$uri=[system.URI] "https://casservername/ews/exchange.asmx"
#$service.Url = $uri
## Optional section for Exchange Impersonation
#$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
#Define Function to convert String to FolderPath
function ConvertToString($ipInputString){
$Val1Text = ""
for ($clInt=0;$clInt -lt $ipInputString.length;$clInt++){
$Val1Text = $Val1Text + [Convert]::ToString([Convert]::ToChar([Convert]::ToInt32($ipInputString.Substring($clInt,2),16)))
$clInt++
}
return $Val1Text
}
#Define Extended properties
$PR_FOLDER_TYPE = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(13825,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);
$folderidcnt = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)
#Define the FolderView used for Export should not be any larger then 1000 folders due to throttling
$fvFolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
#Deep Transval will ensure all folders in the search path are returned
$fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep;
$psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$PR_Folder_Path = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(26293, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
#Add Properties to the Property Set
$psPropertySet.Add($PR_Folder_Path);
$fvFolderView.PropertySet = $psPropertySet;
#The Search filter will exclude any Search Folders
$sfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($PR_FOLDER_TYPE,"1")
$fiResult = $null
#The Do loop will handle any paging that is required if there are more the 1000 folders in a mailbox
do {
$fiResult = $Service.FindFolders($folderidcnt,$sfSearchFilter,$fvFolderView)
foreach($ffFolder in $fiResult.Folders){
$foldpathval = $null
#Try to get the FolderPath Value and then covert it to a usable String
if ($ffFolder.TryGetProperty($PR_Folder_Path,[ref] $foldpathval))
{
$binarry = [Text.Encoding]::UTF8.GetBytes($foldpathval)
$hexArr = $binarry | ForEach-Object { $_.ToString("X2") }
$hexString = $hexArr -join ''
$hexString = $hexString.Replace("FEFF", "5C00")
$fpath = ConvertToString($hexString)
}
"FolderPath : " + $fpath
"Folder Class : " + $ffFolder.FolderClass
}
$fvFolderView.Offset += $fiResult.Folders.Count
}while($fiResult.MoreAvailable -eq $true)
Cheers
Glen