Category Archives: Powershell

GroupSync v0.56 available!

Version 0.56 is out, changes since v0.50:

  • prevent running twice (if scheduled task hangs for some reason)
  • send email notification if logfile is locked
  • replace add-adgroupmember and remove-adgroupmember with set-adgroup because of a known bug in these commands
  • multi-delete protection
  • auto reconnect to Exchange Online when the connection times out + longer timeout
  • additional filtering method for groups: extensionAttribute2
    • If you want to use this instead of the displayName prefix filter, read up on how to switch

Get it here

OnedriveMapper v3.07 released!

Version 3.07 of OneDriveMapper has been released!

  • Azure AD PassThrough SSO now supported
  • Now defaults to TLS V1.2 instead of V1.0 (Powershell default)
  • Auto updater and MSI updates now support changing the config ID
  • Force IE auth mode on Powershell V2 or lower
  • Don’t process AzureADSSO regkeys when using native mode

Get the new version here

Setting a Windows Cookie with Powershell (using InternetSetcookie in WinInet)

As I’m trying to improve OnedriveMapper, I’ve been looking into methods to avoid using Browser Emulation to authenticate with Office 365.

This wasn’t difficult, but storing the cookie posed a challenge. There are no available methods in Powershell to do so, thus I went searching until I ran into a post on Stackoverflow that shows how to store a cookie using C#

Since Powershell can eat C#, this ended up being my working code to set a persistent OS cookie from Powershell:


$source=@"
using System.Runtime.InteropServices;
using System;
namespace Cookies
{
    public static class setter
    {
        [DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool InternetSetCookie(string url, string name, string data);

        public static bool SetWinINETCookieString(string url, string name, string data)
        {
            bool res = setter.InternetSetCookie(url, name, data);
            if (!res)
            {
                throw new Exception("Exception setting cookie: Win32 Error code="+Marshal.GetLastWin32Error());
            }else{
                return res;
            }
        }
    }
}
"@

$compilerParameters = New-Object System.CodeDom.Compiler.CompilerParameters
$compilerParameters.CompilerOptions="/unsafe"

Add-Type -TypeDefinition $source -Language CSharp -CompilerParameters $compilerParameters

[DateTime]$dateTime = Get-Date
$dateTime.AddDays(1)
$str = $dateTime.ToString("R")

[Cookies.setter]::SetWinINETCookieString("https://cookieURL","cookieNAME","value;Expires=$str")

edit: don’t use the Get-Hotfix PS command before you run above code, for some reason it breaks things.

O365Undo updated for O365 Groups

O365Undo is a great script you can use to roll back actions of your user(s) in Office 365. Most likely, actions your user wasn’t aware of but were actually done by a CryptoLocker or by RansomWare.

These nasty virusses can cause havoc on your mapped or synced Sharepoint Online or Onedrive for Business libraries in the form of file level encryption or file name obfuscation.

This new version also protects Office 365 Groups.

Read more or download the script

Using Powershell to check a user’s tenant logon setting in Office 365 (without logging in)

I was interested in being able to see, for any given email, what type of authentication Microsoft requires for that user. This could be Office 365 (Azure AD) native, ADFS, etc.

Powershell can easily help you out:

Add-Type -AssemblyName System.Web
$uid = "YOUR EMAIL ADDRESS"
$uidEnc = [System.Web.HttpUtility]::HtmlEncode($uid)
$res = Invoke-WebRequest -Uri https://login.microsoftonline.com -SessionVariable cookies -Method Get -UseBasicParsing
$stsRequest = ($res.InputFields | where {$_.Name -eq "ctx"}).Value
$flowToken = ($res.InputFields | where {$_.Name -eq "flowToken"}).Value
$canary = ($res.InputFields | where {$_.Name -eq "canary"}).Value
$res = Invoke-WebRequest -Uri "https://login.microsoftonline.com/common/userrealm?user=$uidEnc&api-version=2.1&stsRequest=$stsRequest&checkForMicrosoftAccount=false" -WebSession $cookies -Method GET -UseBasicParsing

The response will contain a redirect to another authentication provider (ADFS) or Azure AD Native. This is an example JSON response:

{"NameSpaceType":"Managed","Login":"mymailaddress@domain.nl","DomainName":"lieben.nu","FederationBrandName":"Lieben Consultancy","TenantBrandingInfo":null,"cloud_instance_name":"microsoftonline.com"}

If you also wish to include Microsoft accounts, set the checkForMicrosoftAccount parameter in the second request to true

AzureAD Connect SSO
If you’re using AzureAD Connect SSO, you can use the above to check if this is correctly set in Office 365. The JSON response will contain a propert is_dsso_enabled, which will be set to True

Prevent duplicate or hanging Powershell processes that run from the Task Scheduler

Many automated processes we write in Powershell are scheduled on a server somewhere and run periodically.
Sometimes, the script may hang. In my experience, the task scheduler setting “Stop the task if it runs longer than:” rarely works properly when a Powershell script hangs. It either thinks it stopped the task, or is unable to.

This can result in memory hogging runaway Powershell processes, locked log files, concurrent user issues, etc etc.

If you want to prevent that from happening, add this function to your script and call it once at the start of your script. It will kill any Powershell process with the same script name that does not match the running process’s Process ID. It requires Powershell 3+:


function preventDoubleSchedule{
    try{
        $scriptFileName = split-path $MyInvocation.PSCommandPath -Leaf
    }catch{$scriptFileName = $Null}
    try{
        [Array]$psProcesses = @(Get-WmiObject Win32_Process -Filter "name like '%Powershell.exe%' and handle != '$pid'" | where {$_})
    }catch{
        Throw
    }
    if($psProcesses.Count -gt 0){
        foreach($psProcess in $psProcesses){
            if($psProcess.CommandLine -like "*$scriptFileName*" -and $scriptFileName){
                ##we've found a Powershell process that is running this script, but does not have the same process ID, lets try to kill it
                try{
                    Stop-Process -Id $psProcess.Handle -Force -Confirm:$False
                }catch{
                    Throw
                }
            }
        }
    }
}

Copy local AD contacts to O365

Recently I needed a basic method to copy over contacts from a local AD to O365, and in cases where a read-write contact already exists; update it. The scenario made sense, as we were working with multiple source AD’s where some had contacts of each other’s mail users, causing adsync conflicts. Thus we decided to take contacts out of ADsync scope and just copy them once.

The logic of the attached script is as follows:

 

 

 

 

 

 

 

 

Note that the script ONLY imports the displayname, primary and all secondary email addresses, and sets an extra X500 address for the legacy exchangeDN to avoid outlook cache hit misses.

If you need it, here’s a download link:

O365ContactImporter.ps1

Powershell v1 and v2 friendly version of Invoke-WebRequest

If you need to use the Invoke-Webrequest on machines that are still running an older Powershell version, this function will help you out.

It sets a script-wide variable called ‘cookiejar’, which will persist any cookies during subsequent calls to this function. You can add customHeaders as a hashtable if you need to. By default the function will also attempt to respond to 401 challenges with the current user credentials.


function JosL-WebRequest{
    Param(
        $url,
        $method="GET",
        $body,
        $trySSO=1,
        $customHeaders
    )
    if($script:cookiejar -eq $Null){
        $script:cookiejar = New-Object System.Net.CookieContainer     
    }
    $maxAttempts = 3
    $attempts=0
    while($true){
        $attempts++
        try{
            $retVal = @{}
            $request = [System.Net.WebRequest]::Create($url)
            $request.TimeOut = 5000
            $request.Method = $method
            if($trySSO -eq 1){
                $request.UseDefaultCredentials = $True
            }
            if($customHeaders){
                $customHeaders.Keys | % { 
                    $request.Headers[$_] = $customHeaders.Item($_)
                }
            }
            $request.UserAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E)"
            $request.ContentType = "application/x-www-form-urlencoded"
            $request.CookieContainer = $script:cookiejar
            if($method -eq "POST"){
                $body = [byte[]][char[]]$body
                $upStream = $request.GetRequestStream()
                $upStream.Write($body, 0, $body.Length)
                $upStream.Flush()
                $upStream.Close()
            }
            $response = $request.GetResponse()
            $retVal.StatusCode = $response.StatusCode
            $retVal.StatusDescription = $response.StatusDescription
            $retVal.Headers = $response.Headers
            $stream = $response.GetResponseStream()
            $streamReader = [System.IO.StreamReader]($stream)
            $retVal.Content = $streamReader.ReadToEnd()
            $streamReader.Close()
            $response.Close()
            return $retVal
        }catch{
            if($attempts -ge $maxAttempts){Throw}else{sleep -s 2}
        }
    }
}

New-DlpComplianceRule usage / example

I was messing around a little with Office 365 Compliance settings using Powershell, as I’d like to configure a large number of tenants with certain Data Loss Prevention (DLP) rules based on sensitive data in Sharepoint Online, Onedrive for Business and  Exchange Online.

I then noticed that it wasn’t possible to use New-DlpComplianceRule in conjunction with predefined or custom sensitive data types, my code + error:

New-DlpComplianceRule -Name "SocialSecurityRule" -Policy "JosLTest" -ContentContainsSensitiveInformatio
n @{Name="Credit Card Number"; minCount="2"} -BlockAccess $True
The value specified in sensitive information is invalid.
+ CategoryInfo : NotSpecified: (:) [New-DlpComplianceRule], InvalidContentC...mationException
+ FullyQualifiedErrorId : [Server=DB5EUR01WS007,RequestId=4a19a0bd-abea-4e06-9dc1-47fc35be9d63,TimeStamp=16-11-201
6 12:02:24] [FailureCategory=Cmdlet-InvalidContentContainsSensitiveInformationException] D7D004DA,Microsoft.Office
.CompliancePolicy.Tasks.NewDlpComplianceRule
+ PSComputerName : eur01b.ps.compliance.protection.outlook.com

Fun thing is, this is actually exactly as Technet shows how it should be done.

So I called support, apparently this is a bug. So, for now, if you want to create a DLP compliance policy and rule, follow this example: Continue reading New-DlpComplianceRule usage / example

How to start a “Trigger Start” windows service with Powershell without elevation / admin rights

Some Windows services can be triggered to start at certain events. These services have ‘Tigger Start’ in their startup name behind whatever you configured (like Manual).

Powershell does not have a native method to register the type of event that triggers such a service, C++ and C# do…..and Powershell can natively run C#.

To trigger a service, you’ll need its guid first:

run sc triggerinfo <SERVICENAME>

This will give you a GUID, for example for the WebClient service:

22b6d684-fa63-4578-87c9-effcbe6643c7

You can then use this GUID in the following script to trigger your service from Powershell 🙂 Continue reading How to start a “Trigger Start” windows service with Powershell without elevation / admin rights