Exchange cmdlets in PowerShell ISE

My colleague made me happy when he described an easy procedure how to import Exchange cmdlets into PowerShell ISE so let me share it here.

  1. Connect to an Exchange server, run Exchange Management Shell and find folder location for Exchange cmdlets by Get-Module and path property.
    8- 10- 2015 7-38-56
  2. Copy the folder to C:\Windows\System32\WindowsPowerShell\v1.0\Modules on your local PC
  3. Restart PowerShell ISE

PowerShell Tip: Get string array from your list

Very easy procedure how to get string array from listed items from somewhere (e.g. txt file).

Well, run PS, declare a variable like $something=” paste your lines then close it by ” and convert it into the array $something=something.split(“`n”)

26- 5- 2015 22-31-55

26- 5- 2015 22-28-41

Of course there are a lot of other possibilities how to declare the same array e.g. $array = @(“10.10.10.26″,”10.10.10.35″,”10.10.10.44”), but anyway the paste option is useful thing ;).

ADFS migration – part 1. – intro

It is a Cloud age and as a Microsoft fan I use and work with Office 365 and Azure (slightly), so I wanted to post a guide about the key stone of the cloud and on-premise authentication – ADFS. As you probably know, Microsoft has so far relased several versions of ADFS and upgrade is not so easy, especially if there is Office 365 involved.

Versions released:

  • ADFS 1.0 (Windows Server 2003)
  • ADFS 1.1 (Windows Server 2008)
  • ADFS 2.0 (Windows Server 2008 R2)
  • ADFS 2.1 (Windows Server 2012)
  • ADFS 3.0 (Windows Server 2012 R2)

I will write articles about upgrade from ADFS 2.0 -> 2.1 -> 3.0

Prerequisites:

  • 3rd party trusted certificate with host name of ADFS service published in external DNS (in my case *.salonovi.cz by Comodo)
  • Office 365 tenant with verified domains (I use tenant zbycha.onmicrosoft.com with verified domain salonovi.cz)
  • ADFS servers and AADSync to synchronize identities from on-premise to cloud.

As this is the intro part of the series, let me use it, to show, how to connect to multiple Office 365 customers. It is easy, and if you need more security,do not fill passwords but use (get-credential) instead.

function Open-Office365Session (){
$customers = @()
$customers +=( ,("0","Cust1","admin@cust1.onmicrosoft.com","Passwordstring"))
$customers +=( ,("1","Cust2","admin@cust2.onmicrosoft.com","Passwordstring"))
$customers +=( ,("2","Cust3","admin@cust3.onmicrosoft.com","Passwordstring"))
Write-host "Configured customers:"  -ForegroundColor DarkYellow
foreach($cust in $customers){Write-host "$($cust[0]) - $($cust[1])"}
Write-host "Select customer to connect:" -ForegroundColor Green
$selection = Read-Host
$usr = "$($customers[$selection][2])"
$pass = "$($customers[$selection][3])"

                $psw = ConvertTo-SecureString -Force -AsPlainText -String "$($pass)"
                $cred = New-Object System.Management.Automation.PSCredential ($usr, $psw)
                $s = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell -Credential $cred -Authentication Basic -AllowRedirection
                import-pssession $s
                import-module msonline
                connect-MsolService -credential $cred
}

Save it to your profile PS1 file: C:\Users\<username>\Documents\WindowsPowershell\Microsoft.PowerShell_profile.ps1

To run function just open new powershell session everytime, you want to connect to Office 365, type: Open-Office365Session and from menu type number you want.

ADFSp1-1

Thats it. I have prepared Office 365 tenant, I have ADFS servers, 3rd party trusted certificate and I can start working on identity sync between On-Premise and Office 365 using AADSync. Latest AAD Sync can be dowloaded from the following link:

http://www.microsoft.com/en-us/download/details.aspx?id=44225

 

PSTip: Get-Random

The first prize of the famous lottery Sportka (Czech Republic)  is currently about 124000 €.

Sportka allows us to select 6 numbers from 49 (array of numbers 1-49) on the lottery ticket.

I wanted to bet just for fun. So why not to generate 6 numbers by PowerShell and Get-Random ;-).

We can use for example:

PS C:\> Get-Random -InputObject (1..49) -Count 6 
19
20
25
6
48
27

PS C:\> 1..49 | Get-Random -Count 6
2
22
48
20
36
9

PS C:\> 1..5 | % { (1..49 | Get-Random -Count 6) -join (",") }
35,1,37,6,31,4
7,10,47,3,37,33
28,43,44,42,21,29
31,49,7,17,14,33
30,20,2,43,29,45

And what about the following?

PS C:\> 1..6 | % { Get-Random -Minimum 1 -Maximum 50 }
48
42
45
40
30
23

It is not good idea because we can get the same numbers. It is improbable at this point but look at this:

PS C:\> 1..6 | % { Get-Random -Minimum 1 -Maximum 6 }
2
2
3
5
5
3

Of course that we can use something like that:

PS C:\> do { if( ($num=(Get-Random -Minimum 1 -Maximum 7) ) -notin $nums ){ [int[]]$nums+=$num } } while ( $nums.count -lt 6 ) $nums
3
4
1
5
6
2

But why to use that horrible command when 1..6 | Get-Random -Count 6 cannot display duplicates :-D.

By the way, the syntax does not support -Count and -Minimum or -Maximum parameters together.

  • Get-Random [[-Maximum] <Object>] [-Minimum <Object>] [-SetSeed <Int32>] [<CommonParameters>]
  • Get-Random [-InputObject] <Object[]> [-Count <Int32>] [-SetSeed <Int32>] [<CommonParameters>]

Feel free to use Get-Random also in the context of:

  • Get-Mailbox | Get-Random
  • Get-Service | Get-Random

New PowerShell 4.0 and its features

When I shortly checked what is new in Windows Management Framework 4.0, I was exciting to see feature Windows PowerShell Desired State Configuration (DSC) because it brings us declarative syntax and basically new concept of scripting, wow.

Useful summary of DSC and declarative vs imperative syntax was described by Ravikanth:

Imperative versus declarative syntax in PowerShell

Windows Management Framework 4.0 Preview is also available so we can use it in practice.

Download: Windows Management Framework 4.0 Preview

Windows Management Framework 4.0 Preview includes updates to Windows PowerShell, Windows PowerShell ISE, Windows PowerShell Web Services (Management OData IIS Extension), Windows Remote Management (WinRM), Windows Management Instrumentation (WMI), the Server Manager WMI provider, and a new feature for 4.0, Windows PowerShell Desired State Configuration (DSC). 

IMPORTANT: Not all Microsoft server applications are currently compatible with WMF 4.0. Before installing WMF 4.0 Preview, be sure to read the WMF 4.0 Preview Release Notes. Specifically, systems that are running the following server applications should not run WMF 4.0 Preview at this time:

  • System Center 2012 Configuration Manager (not including SP1)
  • System Center Virtual Machine Manager 2008 R2 (including SP1)
  • Microsoft Exchange Server 2013, Microsoft Exchange Server 2010, and Microsoft Exchange Server 2007
  • Microsoft SharePoint Server 2013 and Microsoft SharePoint Server 2010
  • Windows Small Business Server 2011 and Windows Small Business Server 2008

(Important part is copied from Windows Management Framework 4.0 Preview Now Available)

PowerShell – Functions and examples how to work with IP addresses (IPv4)

The IP addreass can be declared by data type System.Net.IPAddress. These examples are equivalent:

[System.Net.IPAddress]$Address = "192.168.23.12"
[IPAddress]$Address = "192.168.23.12"

$Address object contains:

Address : 202877120
AddressFamily : InterNetwork
ScopeId :
IsIPv6Multicast : False
IsIPv6LinkLocal : False
IsIPv6SiteLocal : False
IsIPv6Teredo : False
IPAddressToString : 192.168.23.12

TypeName: System.Net.IPAddress
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object comparand)
GetAddressBytes Method byte[] GetAddressBytes()
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Address Property long Address {get;set;}
AddressFamily Property System.Net.Sockets.AddressFamily AddressFamily {get;}
IsIPv6LinkLocal Property bool IsIPv6LinkLocal {get;}
IsIPv6Multicast Property bool IsIPv6Multicast {get;}
IsIPv6SiteLocal Property bool IsIPv6SiteLocal {get;}
IsIPv6Teredo Property bool IsIPv6Teredo {get;}
ScopeId Property long ScopeId {get;set;}
IPAddressToString ScriptProperty System.Object IPAddressToString {get=$this.Tostring();}

As can be seen the IPAddress Class has some limitation for advanced tasks, but the following article uses its functionality really nice related to breaking up decimals in low-/high-bytes and creating IP address ranges (http://powershell.com/cs/blogs/tobias/archive/2011/02/20/creating-ip-ranges-and-other-type-magic.aspx)

New-IPRange

function New-IPRange ($start, $end) {
 # created by Dr. Tobias Weltner, MVP PowerShell
 $ip1 = ([System.Net.IPAddress]$start).GetAddressBytes()
 [Array]::Reverse($ip1)
 $ip1 = ([System.Net.IPAddress]($ip1 -join '.')).Address
 $ip2 = ([System.Net.IPAddress]$end).GetAddressBytes()
 [Array]::Reverse($ip2)
 $ip2 = ([System.Net.IPAddress]($ip2 -join '.')).Address
 for ($x=$ip1; $x -le $ip2; $x++) {
 $ip = ([System.Net.IPAddress]$x).GetAddressBytes()
 [Array]::Reverse($ip)
 $ip -join '.'
 }
}

New-IPRange

Do not use  “0.0.0.0”  and “255.255.255.255” because you will wait on 4294967296 addresses :-).

Of course we can specify IP range through  Microsoft.Exchange.Data.IPRange:

LowerBound : 157.144.33.22
UpperBound : 157.144.33.22
Netmask :
CIDRLength :
RangeFormat : SingleAddress
Size : ::1

LowerBound : 157.144.202.0
UpperBound : 157.144.202.127
Netmask : 255.255.255.128
CIDRLength : 25
RangeFormat : CIDR
Size : ::80

TypeName: Microsoft.Exchange.Data.IPRange

Name MemberType Definition
---- ---------- ----------
CompareTo Method int CompareTo(Microsoft.Exchange.Data.IPRange x)
Contains Method bool Contains(ipaddress ipAddress), bool Contains(Microsoft.Exchange.Data.IPvxAddress ipAddress)
Equals Method bool Equals(System.Object obj), bool Equals(Microsoft.Exchange.Data.IPRange other)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
CIDRLength Property System.Int16 CIDRLength {get;}
LowerBound Property Microsoft.Exchange.Data.IPvxAddress LowerBound {get;}
Netmask Property System.Net.IPAddress Netmask {get;}
RangeFormat Property Microsoft.Exchange.Data.IPRange+Format RangeFormat {get;}
Size Property Microsoft.Exchange.Data.IPvxAddress Size {get;}
UpperBound Property Microsoft.Exchange.Data.IPvxAddress UpperBound {get;}

But I would like to  focus on converting numbers to binary and back (http://www.powershellmagazine.com/2012/10/16/converting-numbers-to-binary-and-back/) because it is basic principle for network addressing.

New-IPv4toBin

function New-IPv4toBin ($ipv4){
 $BinNum = $ipv4 -split '\.' | ForEach-Object {[System.Convert]::ToString($_,2).PadLeft(8,'0')}
 return $binNum -join ""
}

New-IPv4toBin

New-CidrToBin

function New-CidrToBin ($cidr){
 if($cidr -le 32){
 [Int[]]$array = (1..32)
 for($i=0;$i -lt $array.length;$i++){
 if($array[$i] -gt $cidr){$array[$i]="0"}else{$array[$i]="1"}
 }
 $cidr =$array -join ""
 }
 return $cidr
}

New-CidrToBinGet-Broadcast (require New-IPv4toBin)

function Get-Broadcast ($addressAndCidr){
 $addressAndCidr = $addressAndCidr.Split("/")
 $addressInBin = (New-IPv4toBin $addressAndCidr[0]).ToCharArray()
 for($i=0;$i -lt $addressInBin.length;$i++){
 if($i -ge $addressAndCidr[1]){
 $addressInBin[$i] = "1"
 } 
 }
 [string[]]$addressInInt32 = @()
 for ($i = 0;$i -lt $addressInBin.length;$i++) {
 $partAddressInBin += $addressInBin[$i] 
 if(($i+1)%8 -eq 0){
 $partAddressInBin = $partAddressInBin -join ""
 $addressInInt32 += [Convert]::ToInt32($partAddressInBin -join "",2)
 $partAddressInBin = ""
 }
 }
 $addressInInt32 = $addressInInt32 -join "."
 return $addressInInt32
}

Get-BroadcastTest-IPinIPRange (require New-IPv4toBin)

function Test-IPinIPRange ($Address,$Lower,$Mask) {
 [Char[]]$a = (New-IPv4toBin $Lower).ToCharArray()
 if($mask -like "*.*"){
 [Char[]]$b = (New-IPv4toBin $Mask).ToCharArray()
 }else{
 [Int[]]$array = (1..32)
 for($i=0;$i -lt $array.length;$i++){
 if($array[$i] -gt $mask){$array[$i]="0"}else{$array[$i]="1"}
 }
 [string]$mask = $array -join ""
 [Char[]]$b = $mask.ToCharArray()
 }
 [Char[]]$c = (New-IPv4toBin $Address).ToCharArray()
 $res = $true
 for($i=0;$i -le $a.length;$i++){
 if($a[$i] -ne $c[$i] -and $b[$i] -ne "0"){
 $res = $false
 } 
 }
 return $res
}

The first IP address is checked regarding IP range (IP,mask or IP,cidr)

Test-IPinIPRange

New-IPv4fromBin

function New-IPv4fromBin($addressInBin){
 [string[]]$addressInInt32 = @()
 $addressInBin = $addressInBin.ToCharArray()
 for ($i = 0;$i -lt $addressInBin.length;$i++) {
 $partAddressInBin += $addressInBin[$i] 
 if(($i+1)%8 -eq 0){
 $partAddressInBin = $partAddressInBin -join ""
 $addressInInt32 += [Convert]::ToInt32($partAddressInBin -join "",2)
 $partAddressInBin = ""
 }
 }
 $addressInInt32 = $addressInInt32 -join "."
 return $addressInInt32
}

New-IPv4fromBinAdvanced examples:

IPAddressExamplesDownload: IPAddressFunctions.ps1 (click for content)

How to load all functions above into Powershell:

FilePathLoadPS1

PS 2.0 – Remove and compress IIS logs automatically

I created a PS script for removing and compressing IIS log files.

Description

  • Define variables: the log folder $LogFolder (basically it could be %SYSTEMROOT%\System32\LogFiles\W3SVC) and the retention periods ($DeletionRetention = -120, $CompressionRetention = -60).
  • Scripts handles only files with expired retention for attribute LastWriteTime  (e.g. -120 = older than 120 days).
  • It deletes all* files with expired retention ($DeletionRetention) from the log folder. (* Be careful, the folder doesn’t have to contain only logs.)
  • It compresses log files with expired retention ($CompressionRetention) into one zip based on month number from LastWriteTime (IISLogs-Month2-2502132135.zip). Number 2502132135 is the time stamp.
  • The script can be simply scheduled via Windows Task Scheduler if needed (e.g. Trigger: Monthly – last day).HowToScheduleScript

Note

I used in my script zip functions from David Aiken – Compress Files with Windows PowerShell then package a Windows Vista Sidebar Gadget

Script

# VARIABLES
$LogPath = "D:\IISLogs"
$DeletionRetention = -120
$CompressionRetention = -60

# FUNCTIONS
function New-Zip
{
 param([string]$zipfilename)
 set-content $zipfilename ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
 (dir $zipfilename).IsReadOnly = $false
}

function Add-Zip
{
 param([string]$zipfilename)

 if(-not (test-path($zipfilename)))
 {
 set-content $zipfilename ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
 (dir $zipfilename).IsReadOnly = $false 
 }

 $shellApplication = new-object -com shell.application
 $zipPackage = $shellApplication.NameSpace($zipfilename)

 foreach($file in $input) 
 { 
 $zipPackage.CopyHere($file.FullName)
 Start-sleep -milliseconds 500
 }
}

function Get-Zip
{
 param([string]$zipfilename)
 if(test-path($zipfilename))
 {
 $shellApplication = new-object -com shell.application
 $zipPackage = $shellApplication.NameSpace($zipfilename)
 $zipPackage.Items() | Select Path
 }
}

# MAIN SCRIPT
$Items = get-childitem $LogPath

$DeleteLogs = $Items | ? {$_.LastWriteTime -le (Get-Date).adddays($DeletionRetention)}
if($DeleteLogs -ne $null) { $DeleteLogs | %{Remove-Item $_.fullname}}

$Items = get-childitem $LogPath |? {($_.extension -like "*.log") -and ($_.LastWriteTime -le (Get-Date).adddays($CompressionRetention))}

$GroupedItems = $Items | sort LastWriteTime | select @{n='Month';e={$_.LastWriteTime.month}},fullname,length,name | group month

if($GroupedItems -ne $null){
 foreach ($GroupedItem in $GroupedItems){
 $MonthNumber = $GroupedItem.Name
 $Time = Get-Date -Format ddMMyyHHss
 $ZipPath = "$LogPath\IISLogs-Month$MonthNumber-$Time.zip"
 New-Zip $ZipPath
 $GroupedItem.group | select fullname,length | %{
 Get-Item $_.fullname | Add-Zip $ZipPath
 start-sleep -s ($_.Length/20000000)
 }
 if((Get-Zip $ZipPath).length -eq $GroupedItem.Count){
 $GroupedItem.group | %{Remove-Item $_.fullname}
 }else{
 Write-Host "`nERROR - Files are not zipped correctly. Deletion process skipped."
 $GroupedItem.group | %{$_.name}
 }
 }
}

Download

IISLog-Removing&Compressing-v1.ps1

https://skydrive.live.com/redir?resid=E3BA57A6A24B6F3C!137

Note

We can remove logs also by command Forfiles (thank you Lukas).