27 December 2018

Save disk space - Compact - NTFS compress your folders

In the ever ongoing search for disk space on Exchange servers it is possible to compress certain folders with NTFS compression.
You could do this through the file explorer and click yourself silly, or you can turn to PowerShell.
Actually it's just an old command prompt program, but we'll use it in PowerShell.

Now first things first:

DO NOT USE THIS ON YOUR MAILBOXDATABASES!!!

This may seem like a no brainer but I don't want to hear that someone used it because I didn't warn them.
The only thing you can safely use this for is on log files, temp files, etl files and blg files.
If you've read my previous blog post about cleaning up certain logging folders used by exchange (which can be found here:
https://vanbrenk.blogspot.com/2018/02/clean-iis-log-files-blg-and-etl-files.html)

Here are the directories you can compress safely:
As always with scripts/code found on the internet, test it yourself on a test environment.

.\compact.exe /C /S /A /I "C:\Program Files\Microsoft\Exchange Server\V15\Logging\*"                        
.\compact.exe /C /S /A /I "C:\Program Files\Microsoft\Exchange Server\V15\Bin\Search\Ceres\Diagnostics\ETLTraces\*"                        
.\compact.exe /C /S /A /I "C:\Program Files\Microsoft\Exchange Server\V15\Bin\Search\Ceres\Diagnostics\Logs\*"                        
.\compact.exe /C /S /A /I "C:\Program Files\Microsoft\Exchange Server\V15\FIP-FS\Data\ProgramLogArchive\*"                        
.\compact.exe /C /S /A /I "C:\Program Files\Microsoft\Exchange Server\V15\TransportRoles\Logs\FrontEnd\Connectivity\*"                        
.\compact.exe /C /S /A /I "C:\Windows\System32\LogFiles\HTTPERR\*"

14 December 2018

Get-CsWebTicket : Failed to logon with given credentials. Make sure correct user name and password provided.

When trying to login to Skype Online through PowerShell or the Skype for Business control panel you receive the following error:
The WinRM client cannot process the request. Basic authentication is currently disabled in the client configuration. Change the client configuration and try the request again.

Or this one:

Get-CsWebTicket : Failed to logon with given credentials. Make sure correct user name and password provided.

Then the search begins, and brought me to this:
View the current winrm settings to check whether "basic authentication" has been disabled or not.
winrm get winrm/config/client/auth
Auth
    Basic = true [Source="GPO"]
    Digest = true [Source="GPO"]
    Kerberos = true
    Negotiate = true
    Certificate = true
    CredSSP = false

For me it was set with a GPO.
Trying to set it with this:

winrm set config/client/auth/ @{basic="true"}

Update:
Set-Item WSMan:\localhost\Client\Auth\Basic -Value 'True'

Error: Invalid use of command line. Type "winrm -?" for help.
That didn't go as planned.
The tried to set it in the registry with this:
Open regedit as admin and go to:
HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WinRM\Client

Simply change the DWORD from 0 to 1 and then restart the PowerShell console

Well that's nice but no solution.

Then searched for the other error, the one with the Get-CSWebTicket error.
Which led me to this:
Since Skype for Business Control Panel don’t support two-step verification we will need to to set up an “app password” for our Office 365 admin account that has MFA enabled.

Oh, really...and yes I just enabled the force MFA option policy in Azure: "Baseline policy: Require MFA for admins (Preview)".

Created an app password an pasted it in my SkypeOnline PowerShell module and voila I was in once again.

27 November 2018

PowerShell One liners (continuous work in progress)

If you know a nice one liner that should be on here drop me a line.

Install and configure Active Directory Certificate Authority Web Enrollment:
Import-Module ServerManager
Add-WindowsFeature Adcs-Web-Enrollment
Install-AdcsWebEnrollment
Get the top30 largest mailboxes per mailboxdatabase:
Get-Mailbox -Database databasename | Get-MailboxStatistics | Sort-Object totalitemsize -Descending| Select-Object DisplayName,TotalItemSize -First 30
Get all members in a dynamic distribution group:
$dyn =  Get-DynamicDistributionGroup "distributiongroupname"
Get-Recipient -RecipientPreviewFilter $dyn.recipientfilter -OrganizationalUnit $dyn.recipientcontainer
Get emailaddresspolicy setting for all users and set to True:
Get-Mailbox -ResultSize Unlimited | Where {$_.EmailAddressPolicyEnabled -eq $False} | Set-Mailbox -EmailAddressPolicyEnabled $true
Install HTMLOutView module:
Install-Module PSWriteHTML -Force
Install the latest PowerShellCore version:
Invoke-Expression "& { $(Invoke-RestMethod https://aka.ms/install-powershell.ps1) } -UseMSI"
Install the latest PowerShellGet version:
Install-Module PowerShellGet -Force
Find the number of users that connect through OWA:
"C:\Program Files (x86)\Log Parser 2.2\logparser.exe" "SELECT cs-username, Count(*) AS OWAHits from \\sr-xxxxx\d$\IISLogs\W3SVC1\u_ex*.log
 WHERE cs-uri-stem LIKE '/OWA/' AND cs-username IS NOT NULL GROUP BY cs-username ORDER BY OWAHits Desc" -rtp:-1
Find all soft deleted mailboxes
Get-MailboxDatabase | Get-MailboxStatistics | Where { $_.DisconnectReason -eq "SoftDeleted" } | Format-Table DisplayName,Database,DisconnectDate
Permanently delete soft deleted mailboxes
Remove-StoreMailbox -Database MBX02 -Identity "John Doe" -MailboxState SoftDeleted
Delete all soft deleted mailboxes per database
Get-MailboxStatistics -Database MBX02 | where {$_.DisconnectReason -eq "SoftDeleted"} | ForEach
 {Remove-StoreMailbox -Database $_.Database -Identity $_.MailboxGuid -MailboxState SoftDeleted}
Update the Offline Addressbook and the Global Addressbook
Get-OfflineAddressBook | Update-OfflineAddressBook
Get-GlobalAddressList | Update-GlobalAddressList
Update Windows Defender manually:
"%programfiles%\windows defender\mpcmdrun.exe" -signatureupdate -http
Search for IMAP enabled mailboxes:
Get-CASMailbox -ResultSize unlimited | where {$_.ImapEnabled -eq $true} | FL name | out-file C:\temp\imapenabled.txt
Enable Remote Desktop locally:
Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server' -Name fDenyTSConnections -Value 1
Or including the firewall rule:
(Get-WmiObject Win32_TerminalServiceSetting -Namespace root\cimv2\TerminalServices).SetAllowTsConnections(1,1) | Out-Null
(Get-WmiObject -Class "Win32_TSGeneralSetting" -Namespace root\cimv2\TerminalServices 
-Filter "TerminalName='RDP-tcp'").SetUserAuthenticationRequired(0) | Out-Null
Get-NetFirewallRule -DisplayName "Remote Desktop*" | Set-NetFirewallRule -enabled true
Add a user to blocked senders
Set-MailboxJunkEmailConfiguration -Identity "UserName" –BlockedSendersandDomains @{Add="somebody@domain.com"}
Check if set correctly
Get-MailboxJunkEmailConfiguration -Identity "UserName" | FL BlockedSendersandDomains
To Remove a user from blocked senders
Set-MailboxJunkEmailConfiguration -Identity "UserName" –BlockedSendersandDomains @{Remove="somebody@domain.com"}
Delete the file "desktop.ini" from 2 directories deep:
get-childitem -path \\domain.lan\sharename\users\home\*\* -force -filter "desktop.ini" | foreach ($_) {remove-item $_.fullname -force 
-verbose 4>> c:\temp\desktopiniresults.txt}
Set UPN to match Mail Address for Office365 use:
Get-User -OrganizationalUnit "domain.com/OUName" -ResultSize unlimited | Where { -Not [string]::IsNullOrEmpty($_.WindowsEmailAddress) } | 
ForEach { Set-User -Identity $_.Guid.ToString() -UserPrincipalName $_.WindowsEmailAddress.ToString() }
Allow Windows 10 PC in workgroup to manage Hyper-v server:
winrm quickconfig -force
winrm set winrm/config/client ‘@{TrustedHosts=”Name of the Server”}’
Enable protocol logging for IMAP
Set-ImapSettings -Server "CAS01" -ProtocolLogEnabled $true
Disable protocol logging for IMAP
Set-ImapSettings -Server "CAS01" -ProtocolLogEnabled $false
Recreate the Sharedwebconfig.config files for Exchange 2013:
cd %ExchangeInstallPath%\bin
DependentAssemblyGenerator.exe -exchangePath "%ExchangeInstallPath%bin" -exchangePath "%ExchangeInstallPath%ClientAccess" 
-configFile "%ExchangeInstallPath%ClientAccess\SharedWebConfig.config"
DependentAssemblyGenerator.exe -exchangePath "%ExchangeInstallPath%bin" -exchangePath "%ExchangeInstallPath%FrontEnd\HttpProxy" 
-configFile "%ExchangeInstallPath%FrontEnd\HttpProxy\SharedWebConfig.config"
Get the list of network profiles on the system.
Get-NetConnectionProfile
Change the network interface to private, use the network interface index number from the previous command.
Set-NetConnectionProfile -InterfaceIndex 10 -NetworkCategory Private
Get Exchange build number:
Get-ExchangeServer | Format-List Name, Edition, AdminDisplayVersion
Get Exchange Schema version:
"Exchange Schema Version = " + ([ADSI]("LDAP://CN=ms-Exch-Schema-Version-Pt," + ([ADSI]"LDAP://RootDSE").schemaNamingContext)).rangeUpper
Set Default Addressbook Policy and Retention Policy for all mailboxes at once:
Get-Mailbox -ResultSize unlimited | Set-mailbox -AddressBookPolicy "Your AddressBookPolicy" -RetentionPolicy "Your - Default Policy"
Quickly add the Exchange PowerShell module to a regular PowerShell console:
Add-PSSnapin *exchange*
Add multiple aliasses at once:
Set-Mailbox "UserName" -EmailAddresses @{add="UserName01@domain.com","UserName02@domain.com","UserName03@domain.com","UserName04@domain.com",
"UserName05@domain.com","UserName06@domain.com","UserName07@domain.com","UserName08@domain.com","UserName09@domain.com","UserName10@domain.com",
"UserName11@domain.com","UserName12@domain.com","UserName13@domain.com","UserName14@domain.com","UserName15@domain.com","UserName16@domain.com",
"UserName17@domain.com","UserName18@domain.com","UserName19@domain.com","UserName20@domain.com"}
List all mailboxes that have a forwarding address
Get-mailbox -Resultsize Unlimited | select DisplayName,ForwardingAddress | where {$_.ForwardingAddress -ne $Null}
Send Output to Clipboard with PowerShell
Get-EventLog application -Newest 1 | clip
Find specific Help articles with Powershell
Get-Help about_
press tab to cycle through the matches
Find white space (Available new mailbox space) in all databases
Get-MailboxDatabase -Status | sort name | select name,@{Name='DB Size (Gb)';
Expression={$_.DatabaseSize.ToGb()}},@{Name='Available New Mbx Space Gb)';
Expression={$_.AvailableNewMailboxSpace.ToGb()}}
Create Powershell profile
New-Item -path $profile -type file –force
Edit the newly created profile in the following location
C:\Users\Username\Documents\WindowsPowerShell
Load all Powershell available modules at once:
Get-Module -ListAvailable | Import-Module
Turn off shutdown tracker for Windows server
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Reliability"
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Reliability" -Name ShutdownReasonOn -Value 0
Combine multiple files into one;
Get-ChildItem -filter "c:\temp\*.html" | % { Get-Content $_ -ReadCount 0 | Add-Content "c:\temp\combined_files.html" }
Or:
Get-Content -path c:\temp\eventlogs\*.html | Add-Content -Path C:\temp\Eventlogs\combined.html
Get users with imap enabled:
Get-CASMailbox -ResultSize unlimited | Where-Object {$_.imapenabled -eq "true"} | fl name,imapenabled
Get empty AD groups and email the output;
$body=Get-ADGroup -Filter * -Properties Members | where {-not $_.members} | select Name
Send-MailMessage -smtpserver smtp.domain.lan -subject
 "Empty groups" -to "user1@domain.com,user2@domain.com" -from "user@domain.com" -Body ( $Body | out-string )
Set send on behalf of rights;
Set-Mailbox UserMailbox -GrantSendOnBehalfTo UserWhoSends
View who has which permissions on a user mailbox;

Get-MailboxFolderPermission -Identity "alias:\postvak in" | fl 
(for Dutch)

Get-MailboxFolderPermission -Identity "alias:\inbox" | fl 
(for English)

View who has which permissions on a user calendar;

Get-MailboxFolderPermission -Identity alias:\agenda | fl 
(for Dutch)
Get-MailboxFolderPermission -Identity alias:\calendar | fl 
(for English)

Remove user rights on a mailbox/folder for an other user:
Remove-MailboxFolderPermission -Identity username1:\agenda -User username2
Add user rights on a mailbox/folder for an other user:
Add-MailboxFolderPermission -Identity username1:\agenda -AccessRights Publishingeditor -User username2
MAPI encryption enabled or disabled; (for Outlook 2003 clients)

Get-RpcClientAccess | fl encryp*,server
View blocked ActiveSync devices, in "Blocked" state for longer than a month;

Get-ActiveSync Device | Where {$_.DeviceAccessState -eq "blocked"} | Select DeviceModel | ft -auto
Delete "Blocked" activesync devices, in "Blocked" state for longer than a month;

Get-ActiveSync Device | Where {$_.DeviceAccessState -eq "Quarantined" -and $_.FirstSyncTime
 -lt (Get-Date).AddMonths(-1)} | Remove-ActiveSyncDevice

Delete all ActiveSync devices with DeviceAccessState "Blocked";

Get-ActiveSyncDevice | Where {$_.DeviceAccessState -eq "Blocked"} | 
Remove-ActiveSyncDevice

To retrieve all Exchange-related events:


Get-EventLog Application | Where { $_.Source -Ilike “*Exchange*” } 

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.

One way to prevent AutoMapping:
Add-MailboxPermission "shared-mailbox" -User "alias" -AccessRights FullAccess –AutoMapping $False
If the mailbox is still showing in Outlook you have to turn to PowerShell:
Remove-MailboxPermission -Identity shared-mailbox -User user-alias -AccessRights FullAccess -InheritanceType All

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"