07 November 2018

Unable to remove shared mailbox from Outlook profile

As a postmaster or Exchange admin you periodically need to open other users mailboxes to move stuff, restore stuff, add or adjust stuff and so on.

After a while you have a long list of attached mailboxes that you don't need anymore.
And sometimes those mailboxes won't disconnect properly.

How do you get rid of them when the mailboxes are still listed in your folder panel though the mailbox but not listed in Account Settings/Change/More settings/Advanced?

You can view the following attribute in ADUC:

Search for the (shared) mailbox you want to remove, right-click the (shared) mailbox, in the Attribute Editor, double click the msExchDelegateListLink attribute, check if your account is listed there. You can remove your account from the msExchDelegateListLink attribute to clear Automapping.
Restart Outlook and check if the shared mailbox is removed.

30 October 2018

Enable Office365 MFA per user or all users - Search for users with MFA disabled

Enabling all users for MFA is relatively easy with PowerShell, and how to's are found all over the web.
But enabling MFA for one user is a bit more difficult.
Here's how to do it:

Enable MFA per user
#Create the StrongAuthenticationRequirement object and insert required settings
$mf= New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationRequirement
$mf.RelyingParty = "*"
$mfa = @($mf)
#Enable MFA for a user
Set-MsolUser -UserPrincipalName userprinciplename@domain.com -StrongAuthenticationRequirements $mfa


#Enable MFA for all users (use with CAUTION!)
Get-MsolUser -All | Set-MsolUser -StrongAuthenticationRequirements $mfa

Check the settings
$User = Get-msoluser -UserPrincipalName 'user@domain.com' | Select-Object -ExpandProperty StrongAuthenticationRequirements
$User.State

#List All users and MFA status :            
Connect-MsolService            
            
$Result=@()             
$users = Get-MsolUser -All            
$users | ForEach-Object {            
$user = $_            
if ($user.StrongAuthenticationRequirements.State -ne $null){            
$mfaStatus = $user.StrongAuthenticationRequirements.State            
}else{            
$mfaStatus = "Disabled" }            
               
$Result += New-Object PSObject -property @{             
UserName = $user.DisplayName            
UserPrincipalName = $user.UserPrincipalName            
MFAStatus = $mfaStatus            
}            
}            
$Result | Select UserName,UserPrincipalName,MFAStatus            
            
#List only MFA enabled users :            
Connect-MsolService            
            
$Result=@()             
$users = Get-MsolUser -All            
$users | ForEach-Object {            
$user = $_            
if ($user.StrongAuthenticationRequirements.State -ne $null){            
$mfaStatus = $user.StrongAuthenticationRequirements.State            
}else{            
$mfaStatus = "Disabled" }            
               
$Result += New-Object PSObject -property @{             
UserName = $user.DisplayName            
UserPrincipalName = $user.UserPrincipalName            
MFAStatus = $mfaStatus            
}            
}             
$Result | Where-Object {$_.MFAStatus -ne "Disabled"}            
            
#List only MFA disabled users :            
Connect-MsolService            
            
$Result=@()             
$users = Get-MsolUser -All            
$users | ForEach-Object {            
$user = $_            
if ($user.StrongAuthenticationRequirements.State -ne $null){            
$mfaStatus = $user.StrongAuthenticationRequirements.State            
}else{            
$mfaStatus = "Disabled" }            
               
$Result += New-Object PSObject -property @{             
UserName = $user.DisplayName            
UserPrincipalName = $user.UserPrincipalName            
MFAStatus = $mfaStatus            
}            
}            
$Result | Where-Object {$_.MFAStatus -eq "Disabled"}            
            
#Export 365 users MFA status to CSV file :            
Connect-MsolService            
            
$Result=@()             
$users = Get-MsolUser -All            
$users | ForEach-Object {            
$user = $_            
if ($user.StrongAuthenticationRequirements.State -ne $null){            
$mfaStatus = $user.StrongAuthenticationRequirements.State            
}else{            
$mfaStatus = "Disabled" }            
               
$Result += New-Object PSObject -property @{             
UserName = $user.DisplayName            
UserPrincipalName = $user.UserPrincipalName            
MFAStatus = $mfaStatus            
}            
}            
$Result | Select UserName,UserPrincipalName,MFAStatus | Export-CSV "C:\Temp\O365-Users-MFA-Status.csv" -NoTypeInformation -Encoding UTF8            
            
#Create the StrongAuthenticationRequirement object and insert required settings            
$mf= New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationRequirement            
$mf.RelyingParty = "*"            
$mfa = @($mf)            
            
#Enable all disabled users for MFA :            
Connect-MsolService            
            
$Result=@()             
$users = Get-MsolUser -All            
$users | ForEach-Object {            
$user = $_            
if ($user.StrongAuthenticationRequirements.State -ne $null){            
$mfaStatus = $user.StrongAuthenticationRequirements.State            
}else{            
$mfaStatus = "Disabled" }            
               
$Result += New-Object PSObject -property @{             
UserName = $user.DisplayName            
UserPrincipalName = $user.UserPrincipalName            
MFAStatus = $mfaStatus            
}            
}            
$Result | Where-Object {$_.MFAStatus -eq "Disabled"} | Set-MsolUser -StrongAuthenticationRequirements $mfa

Identify users who have registered for MFA and count the number of users.
$Registered = Get-MsolUser -All | where {$_.StrongAuthenticationMethods -ne $null} | Select-Object -Property UserPrincipalName            
$registered            
$registered.count

Identify users who have not registered for MFA and count the number of users.
$NotRegistered = Get-MsolUser -All | where {$_.StrongAuthenticationMethods.Count -eq 0} | Select-Object -Property UserPrincipalName            
$NotRegistered            
$NotRegistered.count

Bulk enable for multiple users in csv file
Enable for multiple users
function Set-MFAUsers {            
    param (            
        [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]            
        [ValidateScript( {Test-Path $_})]              
        [Alias('FullName')]            
        [String] $Path,            
                    
        [ValidateSet('Enabled','Enforced')]            
        [String] $State = 'Enabled'            
    )            
            
    # Set MFA object            
    $MFASetting = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationRequirement -Property @{            
        RelyingParty = "*"            
        State        = $State            
    }            
                
    # Get user list            
    $Users = Get-Content -Path $Path -ReadCount -1            
            
    foreach ($user in $users)             
    {            
         $SetUser = @{            
            UserPrincipalName                = $user            
            StrongAuthenticationRequirements = $MFASetting             
            ErrorAction                      = 'Stop'              
        }            
            
        Try {            
            # Set MFA            
            Set-MsolUser @SetUser            
                        
            # Post Check            
            $ThisUser = Get-msoluser -UserPrincipalName $User |             
                Select-Object -ExpandProperty StrongAuthenticationRequirements            
            
            if ($ThisUser.State -eq $SetUser.StrongAuthenticationRequirements.State) {            
                Write-Host "[SUCCESS] UPN: $user" -ForegroundColor Green            
            }            
            else {            
                Write-Host "[FAILED ] UPN: $user" -ForegroundColor Red            
            }            
        }            
        Catch {            
             Write-Warning -Message $_.Exception.Message            
        }               
    }             
}            
            
Get-ChildItem C:\temp\MFA_Users.txt | Set-MFAUsers -State Enforced

19 October 2018

Exchange 2013 and 2016 - Create Edge subscription

I keep forgetting this:

This is for Exchange 2010, 2013 and 2016 and probaly 2019 but I didn't check this.

Create a new subscription file on the Edge server:
New-EdgeSubscription -FileName C:\Temp\Servername-Edge.xml

Copy the file to a mailbox server and import using this command:
New-EdgeSubscription -FileData ([byte[]]$(Get-Content -Path "C:\Temp\Servername-Edge.xml" -Encoding Byte -ReadCount 0)) -Site "Sitename"

451 4.4.0 Primary target IP address responded with: 454 4.7.0 Temporary authentication failure - Exchange Edge

The error doesn't suggest anything that could point you to a solution.
In my case it wasn't the KB article found here.

Turned out to be a certificate Service assignment that got lost.

The error in the Eventviewer was:

EventID: 12023
Level: Warning
Task Category: Transportservice
Source: MSExchangeFrontEndTransport
Microsoft Exchange could not load the certificate with thumbprint of BAE49XX5021785XX4433FXXA78XX434CXXBD4EXX from the personal store on the local computer. This certificate was configured for authentication with other Exchange servers. Mail flow to other Exchange servers could be affected by this error. If the certificate with this thumbprint still exists in the personal store, run Enable-ExchangeCertificate BAE49XX5021785XX4433FXXA78XX434CXXBD4EXX -Services SMTP to resolve the issue. If the certificate does not exist in the personal store, restore it from backup by using the Import-ExchangeCertificate cmdlet, or create a new certificate for the FQDN or the server enabled for SMTP by running the following command: New-ExchangeCertificate -DomainName serverfqdn -Services SMTP. Meanwhile, the certificate with thumbprint XX728XXD9AD2D55XXE9F9D4BEXX8949AE4DXXCBA is being used.

When running the command:
Get-ExchangeCertificate

Thumbprint                                Services   Subject
----------                                --------   -------
499A246DF957FDF438CD9C7BF5DB070E326B0AF9  ...W...    CN=sr-XXXXX.domain.lan, O=Trend Micro ScanMail for Microsoft Ex...
4272892D9AD2D557DE9F9D4BEB98949AE4D8CCBA  .......    CN=sr-XXXXX.domain.lan
065470FCE311211810679A92A4A2F67708E29398  .......    CN=SkypeforBusiness-OWA
DA9D8609DED5198F1AEEE96E3CCE33ED7323DA5E  IP.WS..    CN=service0.domain.lan
ECB0F4D6FE4BCA7B6DAB79C96F491222F845B3B9  ....S..    CN=service1.domain.nl, O=domain N.V., L=City, S=State, C=NL
E40C46317EE13A419C3B41334EFEA37EFC7E5813  ....S..    CN=sr-XXXXX
2B55508050B8C4269D4DA3EE5C97B346AEAFDF7C  .......    CN=WMSvc-SR-XXXXX
93EEEB92883AB769FD22226B8B78DAB4C60EABD0  ....S..    CN=Microsoft Exchange Server Auth Certificate


Enable-ExchangeCertificate DA9D8609DED5198F1AEEE96E3CCE33ED7323DA5E -Services SMTP

Confirm
Overwrite the existing default SMTP certificate?

Current certificate: 'BAE49EF5021785CA4433F25A7800434CA4BD4E6E' (expires 21-11-2017 13:51:22)
Replace it with certificate: 'DA9D8609DED5198F1AEEE96E3CCE33ED7323DA5E' (expires 26-9-2020 14:12:44)
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [?] Help (default is "Y"): y
A special Rpc error occurs on server SR-XXXXX: The internal transport certificate for the local server was damaged or m
issing in Active Directory. The problem has been fixed. However, if you have existing Edge Subscriptions, you must subs
cribe all Edge Transport servers again by using the New-EdgeSubscription cmdlet in the Shell.
    + CategoryInfo          : ObjectNotFound: (:) [Enable-ExchangeCertificate], InvalidOperationException
    + FullyQualifiedErrorId : [Server=SR-XXXXX,RequestId=30ee7bbb-899d-4cb0-b4e2-8d7862775a41,TimeStamp=19-10-2018 10:
   06:00] [FailureCategory=Cmdlet-InvalidOperationException] FD2ADDFB,Microsoft.Exchange.Management.SystemConfigurati
  onTasks.EnableExchangeCertificate
    + PSComputerName        : sr-XXXXX.domain.lan

As soon as I ran this command mail flow started again.

21 September 2018

Move multiple users from Skype on-premises to Skype Online - Bulk Move-CSUser

There isn't much to be found about this.
I needed to move a list of users from our on-premises Skype for Business 2015 servers to Skype Online.
I know how to do this one user at a time.
$cred = Get-Credential username@tenanant.com            
Move-CsUser -Identity UPN -Credential $cred -Target sipfed.online.lync.com -Confirm:$false

Thats nice and all, but I had a list of 15 users.
I came across this post from Brett Janzen:
He deserves a shit load of traffic to his site for this strike of genius :-)
He created a script to move users from on-premises to Online, to check if they are enabled if the move went well or not and notify you of this by email.

His version can be found here, I made some adjustments because my environment reacted a bit differently.


# Edit this script at lines: 4, 13, (possibly at 17), 23, 32, 46, 51 and 58 to 61            
            
#This 1 liner creates the hash file that we will need in the next script. Needs to be run only the first time, or if password changes            
Read-Host -Prompt "Enter your tenant password" -AsSecureString | ConvertFrom-SecureString | Out-File "cred.txt"            
             
#-------------------------            
#Script starts here:            
#-------------------------            
            
#Time Stamp used for file naming            
$DTStamp = get-date -f "dd-MM-yyyy HH-mm"            
#This uses a hash value of the password for the service user. This will allow us to run the script with out being asked            
$AdminName = "username@tenant.onmicrosoft.com"            
$Pass = Get-Content "cred.txt" | ConvertTo-SecureString            
$credential= new-object -typename System.Management.Automation.PSCredential -argumentlist $AdminName, $Pass            
#Initialize session            
$session = New-CsOnlineSession -Credential $credential #-OverrideAdminDomain "domain.com"            
Import-PSSession $session -AllowClobber            
Set-ExecutionPolicy Unrestricted -force            
#The Beginning of the inspection of users that will be moved            
#Does the userlist file exist?            
If ((test-path "userlist.txt") -eq $False) {            
Send-MailMessage -from "Skype@domain.com" -to "admin@domain.com"-subject "Skype Migrations: No File" -body "Looks like we dont have a file to work with" -smtpServer smtp.domain.com            
}else{            
#check to see if the users are enabled. This will output new file for working with.            
ForEach ($UserToBeMigrated in (Get-Content userlist.txt)) {            
get-csuser $UserToBeMigrated | Where-object {$_.Enabled -eq $False} | Select-object -expandProperty sipaddress | Out-File NotEnabledUsers.txt -append            
get-csuser $UserToBeMigrated | Where-object {$_.Enabled -eq $True} | Select-object -expandProperty sipaddress | Out-File EnabledUsers.txt -append            
}            
#Start of moving users to the cloud with enabledusers.txt            
ForEach ($UserToBeMigrated in (Get-Content EnabledUsers.txt)) {            
Move-CsUser $UserToBeMigrated -Target sipfed.online.lync.com -Credential $credential -Confirm:$False #-verbose #-HostedMigrationOverrideUrl "https://youradmindomainname.online.lync.com/HostedMigration/hostedmigrationservice.svc" -ProxyPool "proxypool.domain.com"             
}            
# Lets give it a pause for any replication delays            
Start-Sleep 60            
#Lets verify the users where migrated            
ForEach ($UserToBeMigrated in (Get-Content EnabledUsers.txt)) {            
Get-CsUser $UserToBeMigrated | where-object {$_.hostingprovider -ne "sipfed.online.lync.com"} |Select-object -ExpandProperty Sipaddress | out-file LeftOvers.txt -Append            
}            
#If there were users that didnt move it will show up in the left overs file            
If ((Get-Content "LeftOvers.txt") -eq $Null) {            
ForEach ($UserToBeMigrated in (Get-Content EnabledUsers.txt)) {            
get-csuser $UserToBeMigrated | select-object SipAddress, HostingProvider | Out-file completedList.txt -append            
}            
#If it passes lets send an email to the admin with some txt files to look through if he or she wants to            
Send-MailMessage -from "Skype@domain.com" -to "admin@domain.com" -subject "Move Complete" -body "Passed on first try. Logs attached" -Attachment "CompletedList.txt","NotEnabledUsers.txt" -smtpServer smtp.domain.com            
#Cleanup!            
rename-item -path completedList.txt -newName "CompletedList- $DTStamp.txt"            
} else {            
#If there is failure email and let the admin know            
Send-MailMessage -from "Skype@domain.com" -to "admin@domain.com" -subject "Move Had Errors" -body "Looks like there was a failure. Logs attached" -attachment "LeftOvers.txt" -smtpServer smtp-lb.domain.com            
#Here we could add another try to see if we can move the users again. This is a work in progress            
}            
}            
#Close them sessions            
get-pssession | remove-pssession            
#Clean Up            
rename-item -path "D:\Scripts\Move-CSUser to Skype Online\leftovers.txt" -newName "_LeftOvers- $DTStamp.txt"            
rename-item -path "D:\Scripts\Move-CSUser to Skype Online\userlist.txt" -newName "_UserList- $DTStamp.txt"            
rename-item -path "D:\Scripts\Move-CSUser to Skype Online\NotEnabledUsers.txt" -newName "_NotEnabledUsers- $DTStamp.txt"            
rename-item -path "D:\Scripts\Move-CSUser to Skype Online\EnabledUsers.txt" -newName "_EnabledUsers- $DTStamp.txt"

21 August 2018

Microsoft Store - Something happened on our end

Look familiar?









Probably a policy is preventing you from accessing the Microsoft store downloads.
Quick fix:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate]
"DoNotConnectToWindowsUpdateInternetLocations"=dword:00000000

17 August 2018

Get notified by email about pending Windows Updates

This all started life by Boe Prox, he created the basic script.
You can find it on his site Learn PowerShell
I added things from comments other blogs and stuff from my own.

Schedule this to run once a week or once a month to receive an email about the number of updates that are pending on the servers that you specify.

You could schedule it for all Exchange servers, or all Skype servers, or the product group you want to see or are responsible for.

Edit the logpath, the product and the email settings and you're good to go.
And remove the spaces around style (Blogger formatting thingy).

The email will look like this:

























Here's the script:


Function Get-PendingUpdate {             
    <#    
      .SYNOPSIS   
        Retrieves the updates waiting to be installed from WSUS   
      .DESCRIPTION   
        Retrieves the updates waiting to be installed from WSUS  
      .PARAMETER Computername 
        Computer or computers to find updates for.   
      .EXAMPLE   
       Get-PendingUpdate
    
       Description 
       ----------- 
       Retrieves the updates that are available to install on the local system 
      .NOTES 
      Author: Boe Prox                                           
                                        
    #>             
                  
    #Requires -version 3.0               
    [CmdletBinding(             
        DefaultParameterSetName = 'computer'             
        )]             
    param(             
        [Parameter(ValueFromPipeline = $True)]             
            [string[]]$Computername = $env:COMPUTERNAME            
        )                 
    Process {             
        ForEach ($computer in $Computername) {             
            If (Test-Connection -ComputerName $computer -Count 1 -Quiet) {             
                Try {             
                #Create Session COM object             
                    Write-Verbose "Creating COM object for WSUS Session"             
                    $updatesession =  [activator]::CreateInstance([type]::GetTypeFromProgID("Microsoft.Update.Session",$computer))             
                    }             
                Catch {             
                    Write-Warning "$($Error[0])"             
                    Break             
                    }             
             
                #Configure Session COM Object             
                Write-Verbose "Creating COM object for WSUS update Search"             
                $updatesearcher = $updatesession.CreateUpdateSearcher()             
             
                #Configure Searcher object to look for Updates awaiting installation             
                Write-Verbose "Searching for WSUS updates on client"             
                $searchresult = $updatesearcher.Search("IsInstalled=0")                 
                         
                #Verify if Updates need installed             
                Write-Verbose "Verifing that updates are available to install"             
                If ($searchresult.Updates.Count -gt 0) {             
                    #Updates are waiting to be installed             
                    Write-Verbose "Found $($searchresult.Updates.Count) update\s!"             
                    #Cache the count to make the For loop run faster             
                    $count = $searchresult.Updates.Count             
                             
                    #Begin iterating through Updates available for installation             
                    Write-Verbose "Iterating through list of updates"             
                    For ($i=0; $i -lt $Count; $i++) {             
                        #Create object holding update             
                        $Update = $searchresult.Updates.Item($i)            
                        [pscustomobject]@{            
                            Computername = $Computer            
                            Title = $Update.Title            
                            KB = $($Update.KBArticleIDs)            
                            SecurityBulletin = $($Update.SecurityBulletinIDs)            
                            MsrcSeverity = $Update.MsrcSeverity            
                            IsDownloaded = $Update.IsDownloaded            
                            Url = $($Update.MoreInfoUrls)            
                            Categories = ($Update.Categories | Select-Object -ExpandProperty Name)            
                            BundledUpdates = @($Update.BundledUpdates)|ForEach{            
                               [pscustomobject]@{            
                                    Title = $_.Title            
                                    DownloadUrl = @($_.DownloadContents).DownloadUrl            
                                }            
                            }            
                        }             
                    }            
                }             
                Else {             
                    #Nothing to install at this time             
                    Write-Verbose "No updates to install."             
                }            
            }             
            Else {             
                #Nothing to install at this time             
                Write-Warning "$($c): Offline"             
            }              
        }            
    }              
}            
            
$output = Get-Pendingupdate -Verbose -ComputerName sr-xxxx,sr-xxxx,sr-xxxx,sr-xxxx,sr-xxxx,sr-xxxx,sr-xxxx | Group-Object ComputerName | Foreach-Object {            
 $_ | Select-Object @{Name='ComputerName';Expr={$_.Name}},            
  @{Name='TotalUpdates';Expr={$_.Count}},            
  @{Name='Critical'; Expr={$_.Group| where MsrcSeverity -eq 'Critical'  | measure | select -expand Count}},            
  @{Name='Important';Expr={$_.Group| where MsrcSeverity -eq 'Important' | measure | select -expand Count}},            
  @{Name='Moderate'; Expr={$_.Group| where MsrcSeverity -eq 'Moderate'  | measure | select -expand Count}},            
  @{Name='NonRated'; Expr={$_.Group| where MsrcSeverity -eq $null       | measure | select -expand Count}}            
} | Select-Object Computername, Totalupdates, Critical, Important, Moderate, Nonrated            
            
# Change variables to your environment            
$Date = Get-Date -Format dd-MM-yyyy              
$logPath = "C:\_Scripts\"            
$product = "Exchange"            
            
# Build table for html files, remove spaces around style            
$style = "< style >BODY{font-family: Arial; font-size: 10pt;}"                                    
$style = $style + "TABLE{border: 1px solid black; border-collapse: collapse;}"                                    
$style = $style + "TH{border: 1px solid black; background: #dddddd; padding: 5px; }"                                    
$style = $style + "TD{border: 1px solid black; padding: 5px; }"                                    
$style = $style + "</ style >"                                    
# End HTML Output file style               
            
#SMTP options for sending the report email                        
$smtpServer = "smtp.domain.lan"                        
$smtpFrom = "Get-PendingWindowsUpdates@domain.nl"                        
$smtpTo = "recipient@domain.nl"                        
$messageSubject = "Windowsupdates available for $product"            
                        
#$body = $output | ConvertTo-Html -head $style -body "Get Windows Updates"            
$output | ConvertTo-HTML -head $style -body "
Windows Updates for $product Servers
" | Out-File "$logPath\output-$date.html"                
            
Send-Mailmessage -To $smtpto -From $smtpfrom -SmtpServer $smtpserver -Subject $messagesubject -Body (Get-Content $logpath\output-$date.html | Out-String) -BodyasHtml            
            
# Remove all html files to prevent filling the disk                        
Remove-Item "$logPath\output-$date.html"