Apr 22

DeleteOldUsers.vbs

'=========================================================================
' DeleteOldUsers.vbs
' VERSION: 1.0
' AUTHOR: Brian Steinmeyer
' EMAIL: sigkill@sigkillit.com
' WEB: http://sigkillit.com
' DATE: 4/22/2014
' USER TERMINATION POLICY:
' - Reset PW
' - Set description to termination date
' - Optionally forward email
' - Move terminated users to specified OU
' COMMENTS: This script works in conjunction with the above user termination
' policy. Specify the OU containing old users and set the number of retention
' days to keep an AD user account after the termination date. You can schedule
' the script to run daily to delete any old users.  to permanently delete
' those users after X amount of days.
'=========================================================================
Option Explicit

' ------ SCRIPT CONFIGURATION ------
Dim oldUserOU: oldUserOU = "OU=Old Users,OU=User,DC=domain,DC=local"
Dim oldUserRetentionDays: oldUserRetentionDays = 30
Dim logResults: logResults = Replace(WScript.ScriptFullName,".vbs","_logs\") & Replace(WScript.ScriptName,".vbs","_") & FixDate(Date()) & Replace(FormatDateTime(Time,4), ":", "") & ".txt"
Dim logRetentionDays: logRetentionDays = 30
Dim emailBlatExe: emailBlatExe = "c:\blat\blat.exe" 'Custom Path to Blat.exe Default is Same Folder as Script
Dim emailProfile: emailProfile = "alert" 'Blat Profile Name See (http://www.blat.net) For Info
Dim emailRecipient: emailRecipient = "alert.admin@domain.com" 'Email to Receive Backup Result
Dim blnEmailOnlyOnDelete: blnEmailOnlyOnDelete = true 'True = Only email results when at least 1 user is deleted
' ------ END CONFIGURATION ------

'MAIN CALLS
Call TerminateOldUsers(oldUserRetentionDays,oldUserOU,logResults)
Call SendResults(logResults,blnEmailOnlyOnDelete,emailBlatExe,emailProfile,emailRecipient)
Call PurgeLogs(Replace(WScript.ScriptFullName,".vbs","_logs\"),logRetentionDays,".txt")

' ***************************************************************************************************
' Sub TerminateOldUsers - Parse Old Users and Delete after X days
' ***************************************************************************************************
Private Sub TerminateOldUsers(intDays,strOU,logName)

    On Error Resume Next    'Start Error Handling

    'Create Log File
    Call Logger(logName, "DATE:" & Now() & vbCrLf & "USER_OU:" & strOU & vbCrLf & "RETENTION_DAYS:" & intDays, True)

    'Search OU For Users
    Dim objConnection: Set objConnection = CreateObject("ADODB.Connection")
    Dim objCommand: Set objCommand =   CreateObject("ADODB.Command")
    objConnection.Provider = "ADsDSOObject"
    objConnection.Open "Active Directory Provider"
    Set objCommand.ActiveConnection = objConnection
    objCommand.Properties("Page Size") = 1000   'Override the Return 1000 Results Default
    Const ADS_SCOPE_SUBTREE = 2
    objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE    'Include Sub OU's
    objCommand.CommandText = "SELECT ADsPath, cn FROM 'LDAP://" & strOU & "' WHERE objectCategory='person' AND objectClass='user'"
    Dim objRecordSet: Set objRecordSet = objCommand.Execute
    'Parse Users
    If objRecordSet.RecordCount > 0 Then
        objRecordSet.MoveFirst
        Dim objUser, objParent, strDescription
        Dim strResult: strResult = ""
        Do Until objRecordSet.EOF
            Set objUser = GetObject(objRecordSet.Fields("ADsPath").Value)
            strDescription = objUser.description
            If strDescription = "" Then
                strDescription = "BLANK"
            End If
            'Only Evaluate Users with Date for Description
            If IsDate(objUser.Description) Then
                If DateDiff("d",objUser.Description,Date) > intDays Then
                    'Delete User Past Retention Date
                    strResult = "DELETE"
                    Set objParent = GetObject(objUser.Parent)
                    objParent.Delete "user", "CN=" & objRecordSet.Fields("cn").Value                   
                Else
                    strResult = "IGNORE"
                End If
            Else
                strResult = "IGNORE"
            End If         
            'Log Results
            If Err.Number <> 0 Then
                Err.Clear
                strResult = "!~ERROR!~"
            End If
            Call Logger(logName, strResult & ":(" & strDescription & "):" & objRecordSet.Fields("ADsPath").Value, False)
            objRecordSet.MoveNext
        Loop
    Else
        Call Logger(logName, "NOUSERS:" & strOU, False)
    End If

    On Error Goto 0 'End Error Handling

End Sub

' *****************************************************************
' Sub SendResults - Parse Log File and Send Results
' *****************************************************************
Private Sub SendResults(logName, blnEmail, emailBlatExe, emailProfile, emailRecipient)

    On Error Resume Next

    ' Create Objects
    Dim objFSO: Set objFSO = CreateObject("Scripting.FileSystemObject")

    'Parse Log File
    Dim intDelete: intDelete = 0
    Dim intTotal: intTotal = 0
    Dim intError: intError = 0
    Dim strLine
    Dim logFile: Set logFile = objFSO.OpenTextFile(logName, 1)
    Do Until logFile.AtEndOfStream
        strLine = logFile.ReadLine
        If InStr(1, strLine, "IGNORE:", 1) Then
            intTotal = intTotal + 1
        End If
        If InStr(1, strLine, "DELETE:", 1) Then
            intTotal = intTotal + 1
            intDelete = intDelete + 1
        End If
        If InStr(1, strLine, "!~ERROR~!:", 1) Then
            intTotal = intTotal + 1
            intError = intError + 1
        End If
        If InStr(1, strLine, "NOUSERS:", 1) Then
            'No Users
        End If
    Loop
    logFile.Close

    'Set Email Subject
    Dim emailSubject: emailSubject = Replace(WScript.ScriptName,".vbs","")
    If intError > 0 Then
        emailSubject =  emailSubject & " - Error"
    Else
        emailSubject = emailSubject & " - Deleted "  & intDelete & " Users"
    End If

    'Set Email Body
    Dim emailBody: emailBody = "TOTAL USERS: " & intTotal & vbCrLf & _
        "ERRORS: " & intError & vbCrLf & _
        "DELETED: " & intDelete

    'Email Results
    If blnEmail = true Then
        If intDelete > 0 Then
            Call SendBlatEmail(emailBlatExe, emailProfile, emailRecipient, emailSubject, emailBody, logName)
        End If
    Else
        Call SendBlatEmail(emailBlatExe, emailProfile, emailRecipient, emailSubject, emailBody, logName)
    End If

    'Cleanup Objects
    Set objFSO = Nothing

    On Error Goto 0

End Sub

' ***************************************************************************************************
' Function SendBlatEmail - Sends Email Using Blat
' ***************************************************************************************************
Private Sub SendBlatEmail(blatPath, blatProfile, strRecipients, strSubject, strBody, strAttachment)

    'Need blat.exe, blat.dll, blat.lib
   On Error Resume Next

    Dim objFSO: Set objFSO = CreateObject("Scripting.FileSystemObject")
    Dim objShell: Set objShell = WScript.CreateObject("WScript.Shell")
    Dim scriptPath: scriptPath = Left(WScript.ScriptFullName,InstrRev(WScript.ScriptFullName,"\"))

    'Ensure Blat Exists
   If Not objFSO.FileExists(blatPath) Then
        If Not objFSO.FileExists(scriptPath & "blat.exe") Then
            Exit Sub
        Else
            blatPath = scriptPath & "blat.exe"
        End If
    End If

    'Set Blat Email Command
   Dim commandText: commandText = chr(34) & blatPath & chr(34)
    commandText = commandText & " -p " & chr(34) & blatProfile & chr(34)
    commandText = commandText & " -to " & strRecipients
    commandText = commandText & " -subject " & chr(34) & strSubject & " " & chr(34) 'Keep Space to Prevent Escaping the Quote
   commandText = commandText & " -body " & chr(34) & strBody & " " & chr(34) 'Keep Space to Prevent Escaping the Quote

    'Append Attachment(s)
   If objFSO.FileExists(strAttachment) Then
        commandText = commandText & " -attach " & chr(34) & strAttachment & chr(34)
    Else
        If objFSO.FileExists(scriptPath & strAttachment) Then
            commandText = commandText & " -attach " & chr(34) & scriptPath & strAttachment & chr(34)
        End If
    End If

    'Send Blat Email
   objShell.run commandText, True

    Set objFSO = Nothing
    Set objShell = Nothing

    If Err.Number <> 0 Then
        Err.Clear
    End If

    On Error Goto 0

End Sub

' ***************************************************************************************************
' Sub PurgeLogs - Deletes Old Log FIles
' ***************************************************************************************************
Private Sub PurgeLogs(logFolder,intDays,strExtension)

    On Error Resume Next

    Dim objFSO: Set objFSO = CreateObject("Scripting.FileSystemObject")
    Dim objFolder: Set objFolder = objFSO.GetFolder(logFolder)
    Dim file
    For Each file In objFolder.Files
        If StrComp(Right(file.Name, Len(strExtension)),strExtension) = 0 Then
            If DateDiff("d", file.DateLastModified, Now) > intDays Then
                objFSO.DeleteFile(file), True
            End If
        End If
    Next

    Set objFSO = Nothing

    If Err.Number <> 0 Then
        Err.Clear
    End If

    On Error Goto 0

End Sub

' ***************************************************************************************************
' Function FixDate - Ensures Single Digit Numbers Have 0 In Front
' ***************************************************************************************************
Function FixDate(strDate)
    Dim arrTemp
    Dim M, D, Y
    arrTemp = Split(strDate, "/")
    M = arrTemp(0)
    D = arrTemp(1)
    Y = arrTemp(2)
    If (M>=0) And (M<10) Then M = "0" & M
    If (D>=0) And (D<10) Then D = "0" & D
    'If (Y>=0) And (Y<10) Then Y = "0" & Y
    FixDate = M & D & Y
End Function

' ***************************************************************************************************
' Function Logger
' ***************************************************************************************************
Private Sub Logger(fileName, logMessage, blnNewLog)

    On Error Resume Next

    Const ForReading = 1, ForWriting = 2, ForAppending = 8
    Dim objFSO: Set objFSO = CreateObject("Scripting.FileSystemObject")
    Dim scriptPath: scriptPath = Left(WScript.ScriptFullName,InstrRev(WScript.ScriptFullName,"\"))
    Dim logName
    If InStr(1,fileName,"\",1) > 0 Then
        logName = fileName
        If objFSO.DriveExists(objFSO.GetDriveName(logName)) Then
            If StrComp(objFSO.GetExtensionName(logName), "", 1) = 0 Then
                If Not objFSO.FolderExists(logName) Then
                    If objFSO.FolderExists(objFSO.GetParentFolderName(logName)) Then
                        objFSO.CreateFolder logName 'Create Folder In Current Path
                       Exit Sub
                    Else
                        Call Logger(objFSO.GetParentFolderName(logName), logMessage, blnNewLog) 'Recurse Creating Parent Folder
                       Call Logger(logName, logMessage, blnNewLog) 'Recurse Creating Current Folder
                       Exit Sub
                    End If
                End If
            Else
                If Not objFSO.FileExists(logName) Then
                    If Not objFSO.FolderExists(objFSO.GetParentFolderName(logName)) Then
                        Call Logger(objFSO.GetParentFolderName(logName), logMessage, blnNewLog)  'Recurse Creating Parent Folder
                       Call Logger(logName, logMessage, blnNewLog)  'Recurse Creating Current Folder
                   End If
                End If
            End If
        End If
    Else
        logName = scriptPath & fileName
    End If
    Dim logFile
    If blnNewLog = True Then
        Set logFile = objFSO.CreateTextFile(logName, True)
    Else
        If objFSO.FileExists(logName) Then
            Set logFile = objFSO.OpenTextFile(logName, ForAppending, True)
        Else
            Set logFile = objFSO.CreateTextFile(logName, True)
        End If
    End If
    logFile.WriteLine logMessage
    logFile.Close
    Set objFSO = Nothing

    On Error Goto 0

End Sub

 

Apr 22

Run Active Directory Management Tools as Another User

There’s quite a few situations where you may need to run Active Directory Management tools like Active Directory Users and Computers with different credentials. For example:

  • Computer is not joined to the domain
  • Need to connect to another domain/forest
  • Logged in as a standard domain user and need to supply different credentials
  • etc…

Step 1 – Install Remote Server Administration Tools (RSAT)

If you are using a 2008 or 2012 WIndows member server, RSAT is a feature you must enable using the directions below:

RSAT Server 2008 or 2012

If you’re using Windows Vista, WIndows 7, Windows 8, or Windows 10 you must download, install, and enable the RSAT feature.  Here are the links to download RSAT:

RSAT Vista SP1

RSAT Windows 7 SP1

RSAT Windows 8

RSAT Windows 8.1

RSAT Windows 10 (By default all features are enabled)

Once you’ve installed RSAT you need to enable the feature (Except Windows 10).  Open Control Panel, click Programs and Features, and click Turn Windows features on or off.  Then enable the following:

Windows Features Enable RSAT

Step 2 – Make Sure You’re on the Domain Network

Make sure you’re on the same network as the Domain Controller.  This simply means, connect to the LAN they’re on, or connect to a VPN if you’re remote.

Step 3 – Run As Commands for AD Management Tools

The key to running AD Management tools is the Runas command in Windows, which allows you to specify alternate credentials.  However, there are a few gotcha’s with runas such as needing to specify the /netonly command when on a non-domain computer.  Here are the commands you’ll need to run to successfully launch the AD Management tools, and all will work whether or not the computer is joined to a domain:

  • C:\Windows\System32\runas.exe – Default path to runas
  • /netonly – Credentials are specified for remote access, which is required for computers not joined to a domain but still works if the computer is on the domain
  • /user: – specify the username by the samaccountname(DOMAIN\user) or UPN(user@domain.local)
  • “mmc %SystemRoot%\system32\snapin.msc” – Microsoft Management Console with the path to the snapin.
C:\Windows\System32\runas.exe /netonly /user:user@domain.local "mmc %SystemRoot%\system32\adsiedit.msc"
C:\Windows\System32\runas.exe /netonly /user:user@domain.local "mmc %SystemRoot%\system32\domain.msc /server=pdc.domain.local"

Note: I’ve added an extra parameter to specify the PDC Emulator, otherwise you may receive the error “You cannot modify domain or trust information because a Primary Domain Controller (PDC) emulator cannot be contacted.”

C:\Windows\System32\runas.exe /netonly /user:user@domain.local "mmc %SystemRoot%\system32\dssite.msc /domain=domain.local"

Note: I’ve added an extra parameter to specify the domain, otherwise you may receive the error “Naming information cannot be located because: The specified domain either does not exist or could not be contacted.”

C:\Windows\System32\runas.exe /netonly /user:user@domain.local "mmc %SystemRoot%\system32\dsa.msc /domain=domain.local"

Note: I’ve added an extra parameter to specify the domain, otherwise you may receive the error “Naming information cannot be located because: The specified domain either does not exist or could not be contacted.”

Step 4 – Applying Run As Commands

Option 1: Run from an Elevated Command prompt

Right-click the command prompt (cmd.exe), select Run as Administrator, and enter one of the runas commands in the previous section.

CMD Runas RSAT

option 2: create shortcut and run as administrator

Right-click in the Windows file explorer, select New, click shortcut, for the location enter one of the runas commands from the previous section, click Next, name the shortcut appropriately, and click Finish.  Whenever you launch the shortcut, right-click it and select Run as Administrator.

Shortcut Runas RSAT

option 3: modify RSAT shortcuts

Under Administrative Tools on the start menu, right-click each RSAT shortcut, click Properties, and modify the target using the appropriate runas command from the previous section.  Whenever you launch the shortcut, right-click it and select Run as Administrator.

Modify RSAT Target
Apr 08

Matching Credit Card Numbers

Overview

Using regular expressions, you can easily match a credit card number.  You may wish to validate legit CC numbers, block financial information in emails, or audit security by finding financial information in documents.  There is no perfect algorithm or regex for detecting potential CCN’s, and there will always be false positives, etc.  Although regular expressions can match a CCN, they cannot confirm incorrect digits.  If you require a more robust solution, you will need to also implement the Luhn algorithm.  Moving forward, I’ll focus on detecting CCN’s in documents or emails.

Credit Card Info

The first 6 digits of a CCN are known as the Issuer Identification Number (IIN), which are used to identify the card issuer; the remaining digits can vary in length and are up to the issuer. Using the IIN and the CCN pre-defined length, we can identify blocks of numbers that belong to each issuer.  The credit card numbers are typically grouped with spaces or dashes in order to make them more readable.  We will need to keep this in mind when matching CCN’s.  I have listed US issuers, their IIN, and the CCN lengths below. For a full list including international issuers see http://en.wikipedia.org/wiki/Bank_card_number.

ISSUER IIN STARTING PATTERN LENGTH
American Express 34, 37 15
Diners Club 300-305, 309, 36, 38-39 15
Discover 6011, 622126-622925, 644-649, 65 16
JCB 3528-3589 16
MasterCard 50-55 16
Visa 4 16 or 13 on old cards

The Basics Validating Credit Cards

If we wanted to validate a credit card, you would first want to remove any spaces or dashes from the input.  Once the input is clean we can use a typical regular expression to match potential valid CCN’s. The below regex’s were originally taken from http://www.regular-expressions.info/creditcard.html, but I’ve updated the out of date expressions in accordance to the latest IIN changes as per the Wikipedia article listed above:

American Express

^3[47][0-9]{13}$

 

Diners Club

^3(?:0[0-5]|09|[68][0-9])[0-9]{11}$

 

Discover

^6(?:011|22[0-9]|5[0-9]{2})[0-9]{12}$

 

JCB

^35(?:2[89]|[3-8][0-9])[0-9]{12}$

 

Mastercard

^5[0-5][0-9]{14}$

 

Visa

^4[0-9]{12}(?:[0-9]{3})?$

 

However, if you are unable to strip the spaces and dashes out prior to validating the CCN, you’ll quickly find many shortcomings.  The above regex’s will not account for spaces or dashes as printed on the front of the card and will only detect a CCN when it’s the only thing on a line.  Obviously, this will not give us the results we desire for finding CCN’s in a document or email.  Instead, we will want to use \b to match on a word boundary instead of the carrot(^) and the dollar($).  In addition, we also want to add exceptions before and after the CCN we’re checking, which will help reduce false positives; this will allow us to eliminate items such as hyperlinks, order #’s, etc.  We can now use the below regex to surround each CC issuer’s rules that we want to detect:

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s|ID:\s)CCN(?!\/)\b

 

  • Starting Position is a word boundary
  • Previous Character is not:
    • period(.)
    • left angle bracket(<)
    • right angle bracket(>)
    • dash()
    • plus(+)
    • forward slash(/) – Ignore false positives like http://www.domain.com/################/
    • Open parenthesis – Ignore false positives like (################)
    • Equal (=) – Ignores false positives like http://www.domain.com?si=################
    • Pound, Colon, Space(#:  ) – Ignore false positives like Order#: ################
    • dash, space(– )
    • ID, colon, space(ID: )
  • CCN – Represents the regex used to represent each issuers credit card number
  • Next character is not:
    • forward slash(/) – Ignore false positives like http://www.domain.com/################/
  • Ending position is a word boundary

Matching Credit Cards in a Document or Email

By doing a slight re-write of CC regex’s and combining it with our above wrapper, we can easily detect a CCN’s in a document or email. However, the oneliner for Discover CCN’s is quite long, and some systems limit the length of regex’s. Due to this, I’ve provided the oneliner plus shorter versions split up by the IIN.

American Express

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s|ID:\s)3[47](?:\d{13}|\d{2}\s\d{6}\s\d{5}|\d{2}\-\d{6}\-\d{5})(?!\/)\b

 

Diners Club

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s|ID:\s)3(?:0[0-5]|09|[689]\d)\d(?:\d{10}|\s\d{6}\s\d{4}|\-\d{6}\-\d{4})(?!\/)\b

 

Discover (Oneliner)

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s|ID:\s)(?:6011\d{12}|6011(\s\d{4}){3}|6011(\-\d{4}){3}|64[4-9]\d{13}|64[4-9]\d(\s\d{4}){3}|64[4-9]\d(\-\d{4}){3}|65\d{14}|65\d{2}(\s\d{4}){3}|65\d{2}(\-\d{4}){3}|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5])\d{10}|622(1\s2[6-9]|1\s[3-9][0-9]|[2-8]\s[0-9]{2}|9\s[0-1][0-9]|9\s2[0-5])\d{2}(\s\d{4}){2}|622(1\-2[6-9]|1\-[3-9][0-9]|[2-8]\-[0-9]{2}|9\-[0-1][0-9]|9\-2[0-5])\d{2}(\-\d{4}){2})(?!\/)\b

 

Discover (6011,644-649,65 IIN’s)

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s|ID:\s)6(?:011|4[4-9]\d|5\d{2})(?:\d{12}|(\s\d{4}){3}|(\-\d{4}){3})(?!\/)\b

 

Discover (622 IIN No Delimiter)

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s|ID:\s)622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5])\d{10}(?!\/)\b

 

Discover (622 IIN Space Delimiter)

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s)622(1\s2[6-9]|1\s[3-9][0-9]|[2-8]\s[0-9]{2}|9\s[0-1][0-9]|9\s2[0-5])\d{2}(\s\d{4}){2}(?!\/)\b

Note: Office 365 has a 128 character limit for the regex expression.  I have modified the default wrapper by removing a few items to keep this at 128 characters

 

Discover (622 IIN Dash Delimiter)

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s)622(1\-2[6-9]|1\-[3-9][0-9]|[2-8]\-[0-9]{2}|9\-[0-1][0-9]|9\-2[0-5])\d{2}(\-\d{4}){2}(?!\/)\b

Note: Office 365 has a 128 character limit for the regex expression.  I have modified the default wrapper by removing a few items to keep this at 128 characters

 

JCB

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s|ID:\s)35(?:2[89]|[3-8]\d)(?:\d{12}|(\s\d{4}){3}|(\-\d{4}){3})(?!\/)\b

 

MasterCard

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s|ID:\s)5[0-5](?:\d{14}|\d{2}(\s\d{4}){3}|\d{2}(\-\d{4}){3})(?!\/)\b

 

Visa

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s|ID:\s)4(?:\d{11}|\d{3}\s(\d{4}\s){2}|\d{3}\-(\d{4}\-){2})\d(\d{3})?(?!\/)\b

If we test the above regex in an online tester, we can verify it’s working as expected. As you can see, our regex is capturing our test Visa CCN’s and missing a lot of false positives:

Visa Regex Test

Real World Ex: Blocking Emails with Credit Card Numbers in Office 365

Now that we’ve established our regex’s, let’s apply it to a real world example. For our example, we’ll block inbound/outbound email in Office 365. Please note, Office 365 transport rules only allow 128 characters in a regex, and due to this we’ll need to use multiple regex’s for matching Discover.  Also note, two of the Discover Regex’s I used above have a modified wrapper to keep them at the 128 character limit.

  1. Login to the Office 365 Admin Portal (https://portal.microsoftonline.com)
  2. Click Admin then click Exchange
  3. Under the Exchange Admin Center, click Mail Flow
  4. Click the Rules tab
  5. Click the + to create a new rule
    1. Name: Block Emails with Credit Card Numbers
    2. Apply this rule if: The subject or body matches:
      1. Paste each CCN Regex
    3. Do the following:  Reject the message with the explanation
      1. Rejection Reason: Your message was blocked due to the detection of a Credit Card Number
  6. Click Save
    1. Note: Mail flow rules normally take 30-35 minutes to replicate in Office 365
Apr 04

Block Outbound Email for Specific Users

Overview

There are a few situations where you may need to restrict certain users from sending email to external users.  For example, you may have part time employees that only need to send email to internal users OR you might have an employee who’s about to get terminated and don’t want them emailing clients.  Fortunately, in Office 365 Exchange you can create a Mail Flow Rule to accomplish this.

Create Distribution Group to Define Users to Block Outbound Email

In order for the mail flow rule to see the group, it must be a distribution group.  However, you can easily hide it from the GAL so your users don’t see it.  Many organizations use CustomAttribute15 to define what displays in there GAL.  If that’s your case, simply do not define CustomAttribute15 or define it to a value so it does not show in your GAL; otherwise, set the attribute to Hide group from Exchange Address Lists.

  1. Create a new distribution group
    1. Name: Block Outbound Email
    2. Email: blockoutboundemail@<company>.onmicrosoft.com
    3. Members: Add any user you want to block from sending outbound emails to external recipients (They will only be able to send to internal recipients)
  2. If you are using Office 365 in a Hybrid Deployment, make sure you use dirsync to synchronizes your new group

Create Mail Flow Rule

In this example, we will prevent a user from sending emails to any external recipients, but they will still be able to send to internal recipients.

  1. Login to the Office 365 Admin Portal https://portal.microsoftonline.com
  2. Click Admin then click Exchange to open the Exchange Admin CenterOpen Exchange Admin Center
  3. Click mail flow then click on the Rules tab
  4. Click the + symbol and click Create a new rule       Create New Rule
  5. Name the rule Block Outbound Emails to External Recipients
  6. Under Apply this rule if, click the recipient is located
    1. Select Outside the organization and click OK
  7. Click More Options to add another condition
  8. Click Add Condition
  9. On the new condition, select the sender is a member of this group
    1. Search and select the group Block Outbound Emails and click OK
    2. Note: Despite the wording stating “member of this group”, you can select a user instead of a group.  However, it’s easier to manage and you do not need to wait for the mail flow rule to propagate on 365, which can take up to an hour in my testing.
  10. Under Do the following, select Block the message then click delete the message without notifying anyone, and click OK
  11. Click Save

IMPORTANT NOTE:  It can take up to 45 minutes for Microsoft’s back end to fully synchronize rules!  This means any new or modified rules can take up to 45 minutes to take effect!

Block Outbound Email

 

 

 

Apr 04

Delivery Report in Outlook or Outlook Web App

Overview

When using Outlook or Outlook Web App (OWA) in an Office 365 or Exchange environment, you can track the message from the client side.  Both Outlook and OWA allow you to view a delivery report in order to confirm a message was delivered when the recipient claims they have not received it or if it’s taking a long time to deliver.  Delivery reports work for both internal and external recipients.

View a Delivery Report in Outlook

  1. In Outlook, go to your Sent Items folder
  2. Locate the message you want to track and open it
  3. Click File, click Info, and click Open Delivery Report

Outlook Message Delivery Report

View a Delivery Report in Outlook Web App (OWA)

If you are using any other email client than Outlook (mobile device, OWA, etc), you can use OWA to view a delivery report.

  1. Login to OWA at https://portal.microsoftonline.com
  2. Click the Gear Icon, then click Options
  3. Click Organize Email then click Delivery Reports
  4. Enter your search criteria, click Search
  5. Select the email you want to track and click the Pencil Icon to view the delivery report

OWA Delivery Report

Review Delivery Report

Internal delivery reports will show Delivered upon success delivering.  Also note, Office 365 Exchange only keeps message tracking data for 14 days.

Delivery Report Internal

External delivery reports will only show Transferred which means it successfully sent out from your mail server.  However, this does not guarantee the recipient received the email because there can be issues on the recipients email server.

Delivery Report External

Apr 04

365Licenses.ps1

#=========================================================================
# 365Licenses.ps1
# VERSION: 1.0
# AUTHOR: Brian Steinmeyer
# EMAIL: sigkill@sigkillit.com
# WEB: http://sigkillit.com
# DATE: 4/4/20114
# REQUIREMENTS:
# 1) Microsoft Online Services Sign-In Assistant for IT Professionals
# -(http://www.microsoft.com/en-gb/download/details.aspx?id=28177)
# 2) Windows Azure Active Directory Module for Windows PowerShell
# -(http://technet.microsoft.com/en-us/library/jj151815.aspx#bkmk_installmodule)
# COMMENTS: This script is intended to retrieve Office 365 licensing information
# by accepted domains. It will provide the active license count by domain,
# estimate the cost per domain, and provide the number of unused licenses.
#=========================================================================

# ------ SCRIPT CONFIGURATION ------
# Define License Unit Cost
$intCost = 3.88
# ------ END CONFIGURATION ------

# Connect to Microsoft Online
write-host "Connecting to Office 365..."
Import-Module MSOnline
Try {
Connect-MsolService -ErrorAction Stop
} Catch {
Write-Host $error[0].Exception -ForegroundColor Red -BackgroundColor Black
Write-Host "Error Connecting to Office 365... Quitting Script!" -ForegroundColor Red -BackgroundColor Black
Break
}

# Get Office 365 Accepted Domains and Active User Licenses
Try {
$arrDomains = @(Get-MsolDomain)
} Catch {
Write-Host "Error Retrieving Office 365 Accepted Domains... Quitting Script!" -ForegroundColor Red -BackgroundColor Black
Break
}
$arrCompany = @()
$TotalLicenses = 0
$TotalCost = 0
foreach ($d in $arrDomains){
$domain = $d.Name
write-host ("PROCESSING: " + $domain.ToUpper())
$users = Get-MsolUser -All | where {$_.isLicensed -eq "True" -and $_.UserPrincipalName.Contains($domain)} | Select DisplayName, UserPrincipalName -ExpandProperty Licenses | Select DisplayName, UserPrincipalName, AccountSkuID
If ($users.count -ne $null){
$i = $users.count
$users | format-table
$users | Export-Csv ("365_Licenses_" + $domain.replace(".","_") + ".csv")
}
Else{
$i = 0
Write-Host "0 Licenses<code>n</code>n"
}
$objCompany = New-Object -TypeName PSObject
$objCompany | Add-Member -Name 'Domain' -MemberType Noteproperty -Value $domain
$objCompany | Add-Member -Name 'Licenses' -MemberType Noteproperty -Value $i
$objCompany | Add-Member -Name 'Cost' -MemberType Noteproperty -Value ("{0:C2}" -f ($i * $intCost))
$arrCompany += $objCompany
$TotalLicenses += $i
$TotalCost += ($i * $intCost)

}

# Get Company Licensing Info
Try {
$companyLicenses = Get-MsolAccountSku | Select AccountSkuId, ActiveUnits, WarningUnits, ConsumedUnits
} Catch {
Write-Host $error[0].Exception -ForegroundColor Red -BackgroundColor Black
Write-Host "Error Retrieving Office 365 Account Info... Quitting Script!" -ForegroundColor Red -BackgroundColor Black
Break
}
$objCompany = New-Object -TypeName PSObject
$objCompany | Add-Member -Name 'Domain' -MemberType Noteproperty -Value "TOTAL ACTIVE LICENSES"
$objCompany | Add-Member -Name 'Licenses' -MemberType Noteproperty -Value $TotalLicenses
$objCompany | Add-Member -Name 'Cost' -MemberType Noteproperty -Value ("{0:C2}" -f $TotalCost)
$arrCompany += $objCompany

$unusedLicenses = ($companyLicenses.ActiveUnits - $companyLicenses.ConsumedUnits)
$unusedCost = ($unusedLicenses * $intCost)
$objCompany = New-Object -TypeName PSObject
$objCompany | Add-Member -Name 'Domain' -MemberType Noteproperty -Value "TOTAL UNUSED LICENSES"
$objCompany | Add-Member -Name 'Licenses' -MemberType Noteproperty -Value $unusedLicenses
$objCompany | Add-Member -Name 'Cost' -MemberType Noteproperty -Value ("{0:C2}" -f $unusedCost)
$arrCompany += $objCompany

$objCompany = New-Object -TypeName PSObject
$objCompany | Add-Member -Name 'Domain' -MemberType Noteproperty -Value "GRAND TOTAL LICENSES"
$objCompany | Add-Member -Name 'Licenses' -MemberType Noteproperty -Value $companyLicenses.ActiveUnits
$objCompany | Add-Member -Name 'Cost' -MemberType Noteproperty -Value ("{0:C2}" -f ($companyLicenses.ActiveUnits * $intCost))
$arrCompany += $objCompany

# Display Statistics
$companyLicenses | Format-Table -Auto
$arrCompany | Format-Table -Auto