Wednesday, April 26, 2017

DiskPart and PowerShell–part 1

DiskPart and PowerShell–part 1

http://ift.tt/2oxlxFy

An attendee at the Summit made the statement that the DiskPart utility didn’t have any equivalent in PowerShell. That’s not strictly true as the storage module provides a lot of functionality that maps to diskpart functionality.

The module contents include:

PS> Get-Command -Module Storage | select name

Name
----
Disable-PhysicalDiskIndication
Disable-StorageDiagnosticLog
Enable-PhysicalDiskIndication
Enable-StorageDiagnosticLog
Flush-Volume
Get-DiskSNV
Get-PhysicalDiskSNV
Get-StorageEnclosureSNV
Initialize-Volume
Write-FileSystemCache
Add-InitiatorIdToMaskingSet
Add-PartitionAccessPath
Add-PhysicalDisk
Add-TargetPortToMaskingSet
Add-VirtualDiskToMaskingSet
Block-FileShareAccess
Clear-Disk
Clear-FileStorageTier
Clear-StorageDiagnosticInfo
Connect-VirtualDisk
Debug-FileShare
Debug-StorageSubSystem
Debug-Volume
Disable-PhysicalDiskIdentification
Disable-StorageEnclosureIdentification
Disable-StorageHighAvailability
Disable-StorageMaintenanceMode
Disconnect-VirtualDisk
Dismount-DiskImage
Enable-PhysicalDiskIdentification
Enable-StorageEnclosureIdentification
Enable-StorageHighAvailability
Enable-StorageMaintenanceMode
Format-Volume
Get-DedupProperties
Get-Disk
Get-DiskImage
Get-DiskStorageNodeView
Get-FileIntegrity
Get-FileShare
Get-FileShareAccessControlEntry
Get-FileStorageTier
Get-InitiatorId
Get-InitiatorPort
Get-MaskingSet
Get-OffloadDataTransferSetting
Get-Partition
Get-PartitionSupportedSize
Get-PhysicalDisk
Get-PhysicalDiskStorageNodeView
Get-PhysicalExtent
Get-PhysicalExtentAssociation
Get-ResiliencySetting
Get-StorageAdvancedProperty
Get-StorageDiagnosticInfo
Get-StorageEnclosure
Get-StorageEnclosureStorageNodeView
Get-StorageEnclosureVendorData
Get-StorageFaultDomain
Get-StorageFileServer
Get-StorageFirmwareInformation
Get-StorageHealthAction
Get-StorageHealthReport
Get-StorageHealthSetting
Get-StorageJob
Get-StorageNode
Get-StoragePool
Get-StorageProvider
Get-StorageReliabilityCounter
Get-StorageSetting
Get-StorageSubSystem
Get-StorageTier
Get-StorageTierSupportedSize
Get-SupportedClusterSizes
Get-SupportedFileSystems
Get-TargetPort
Get-TargetPortal
Get-VirtualDisk
Get-VirtualDiskSupportedSize
Get-Volume
Get-VolumeCorruptionCount
Get-VolumeScrubPolicy
Grant-FileShareAccess
Hide-VirtualDisk
Initialize-Disk
Mount-DiskImage
New-FileShare
New-MaskingSet
New-Partition
New-StorageFileServer
New-StoragePool
New-StorageSubsystemVirtualDisk
New-StorageTier
New-VirtualDisk
New-VirtualDiskClone
New-VirtualDiskSnapshot
New-Volume
Optimize-StoragePool
Optimize-Volume
Register-StorageSubsystem
Remove-FileShare
Remove-InitiatorId
Remove-InitiatorIdFromMaskingSet
Remove-MaskingSet
Remove-Partition
Remove-PartitionAccessPath
Remove-PhysicalDisk
Remove-StorageFileServer
Remove-StorageHealthSetting
Remove-StoragePool
Remove-StorageTier
Remove-TargetPortFromMaskingSet
Remove-VirtualDisk
Remove-VirtualDiskFromMaskingSet
Rename-MaskingSet
Repair-FileIntegrity
Repair-VirtualDisk
Repair-Volume
Reset-PhysicalDisk
Reset-StorageReliabilityCounter
Resize-Partition
Resize-StorageTier
Resize-VirtualDisk
Revoke-FileShareAccess
Set-Disk
Set-FileIntegrity
Set-FileShare
Set-FileStorageTier
Set-InitiatorPort
Set-Partition
Set-PhysicalDisk
Set-ResiliencySetting
Set-StorageFileServer
Set-StorageHealthSetting
Set-StoragePool
Set-StorageProvider
Set-StorageSetting
Set-StorageSubSystem
Set-StorageTier
Set-VirtualDisk
Set-Volume
Set-VolumeScrubPolicy
Show-VirtualDisk
Start-StorageDiagnosticLog
Stop-StorageDiagnosticLog
Stop-StorageJob
Unblock-FileShareAccess
Unregister-StorageSubsystem
Update-Disk
Update-HostStorageCache
Update-StorageFirmware
Update-StoragePool
Update-StorageProviderCache
Write-VolumeCache

In this mini series I’m going to go through a number of the diskpart options and show you how to do the same with the Storage module cmdlets.

I’m not sure if all diskpart options are available but it’ll be fun to find out.

The Storage module was introduced with Windows 8/Server 2012.

ls $pshome\modules\storage

or

Get-ChildItem $pshome\modules\storage

if you prefer shows a number of cdxml files. This means that the cmdlets are based on CIM classes which you can see at

Get-CimClass -Namespace ROOT/Microsoft/Windows/Storage

These classes aren’t available on versions of Windows prior to Windows 8/Server 2012.

I’ll also have a look at some of these classes to see if there’s anything we can do that isn’t covered directly by the storage module cmdlets.



Powershell

Powershell

via Richard Siddaway's Blog http://ift.tt/1nN0D9u

April 26, 2017 at 05:57AM

AD_bulk_new_OU

AD_bulk_new_OU

http://ift.tt/2q8jF35

Active Directory, bulk create OU's with defined sub OU's

Powershell

Powershell

via PowerShell Script Repository http://poshcode.org/

April 25, 2017 at 09:02PM

Monday, April 24, 2017

Always Ask for the API

Always Ask for the API

http://ift.tt/2oFnAmd

Take this hypothetical, totally-didn’t-recently-come-up-at-work, scenario: your business uses two disparate cloud services and needs them to share data, but they don’t integrate natively.

You, the scripting magician that you are, can take an export from the first service, manipulate it however it needs to be manipulated and then import it to the other service. You could do this in reverse. You could do this all day, every day; it’s a task right up your alley.

But you really don’t need to make this harder than it needs to be.

Hard? This sounds like a piece of cake!

Yeah sure. At first look it really is a simple task.

You read in a CSV, make some changes to the resulting objects, maybe pull in some auxiliary info from an internal system and finally spit out a new CSV that can be uploaded to the second service.

But take a step back and consider: how are you getting that initial CSV?

Chances are that you may not have your own access to the service in question, or you may have limited access. This is especially true if it’s “owned” by another business unit outside of IT, like Human Resources. This means that you likely won’t be able to export a report as a CSV and you’ll need someone else to do it for you and probably email it to you.

Now you’ve got to figure out how to:

  • Check for to see if the email is in your inbox.

  • Save the attachment somewhere.

  • Read the file in and do all the processing mentioned above.

  • Clean up the file.

You’ve also got to worry about the staff member you’ve roped into this scheme remembering to do it. It’ll also never be sent at the same time, so you’re script/process will need to run in a loop until the email arrives.

Also, the whole point of automation is to rule out (or limit/mitigate) human error. Are you 100% sure that there is no chance that the person running the report won’t accidentally generate the wrong one. The report you get could end up with missing columns, extra columns. Maybe it has the right info, but in the “wrong” order.

Well, that escalated quickly

It sure did! But fear not, there is (usually) and easier way.

Firstly, if this is a bigger cloud provider, chances are they already have an accessible and well documented API. In which case, just use that… and why were you even considering doing any of the above?!

However, if this is more of a bespoke service, then instead of roping someone from the business unit into running a report all the time for you, ask them if they have a technical contact or account manager with the company providing the cloud service.

With contact details in hand, flick them an email. Include the person you’ve been talking to within your own company so they are aware of what you’re asking for and can authorize it if need be.

Keep the email short and simple, explain that you’re attempting to get data out of system x using an API (maybe mention a preference for JSON if you’re on that PowerShell train). Ask if it’s possible and what the authorization and development process is.

You’ll likely get a “sure, no problem!” type response and details on the specific info the company needs. This will likely include the specific info you want exposed and how often you’ll be polling it.

When I’ve done this in the past, after specifying the info I’m after they will generate an example of the result as a CSV so you can eye ball that the result is what you’re looking for and then they’ll hand it off to their developers to set it all up.

When the developers have done their thing, you should get some instructions, a URL and an authorization key. Time to start Invoke-RestMethoding!

So I can have my cake and eat it too?

Getting access to the API will make your job dramatically easier and the end product more robust and reliable.

But it isn’t free (unless you have a generous provider, a great support contract or negotiate it when procuring the service.)

In every case when I have been through this process we’ve been changed an hourly fee for two hours of development time. It’s also possible that your provider may do the setup for free but then charge per request against the API.

It’s pretty easy to justify this cost, assuming your provider doesn’t absolutely gouge you, when you consider the amount of time that it’ll take you to develop the process that an API eliminates. Also consider the time you’ll have to give up when something goes wrong; an extra column causing the script to fail? That’s more of your time that would have been freed up with the API being in place.

As an aside if you’re having trouble working your hours into a cost/benefit analysis keep in mind your time does have a cost to the business. Chances are it’s actually higher than the hourly rate that you’re paid. Some managers would happily give you a number you can use in a business case, others may have never thought about it before.

In any case, it can’t hurt to at least ask for the API, that much costs nothing.



Powershell

Powershell

via Planet PowerShell http://ift.tt/2ozzxeJ

April 24, 2017 at 12:52PM

PowerShell Hash Tables and Calculated Properties Basics

PowerShell Hash Tables and Calculated Properties Basics

http://ift.tt/2pc2JZv

This video is a basic overview of how to use hash tables and calculated properties to make PowerShell data more usable.

<#
.SYNOPSIS
    How to use Hash Table and Calculated Properties

.DESCRIPTION
    A basic walk through of how to use Hash Tables and Calculated properties

.PARAMETER 


Powershell

Powershell

via Planet PowerShell http://ift.tt/2ozzxeJ

April 24, 2017 at 10:57AM

Powershell Tip: Escaping special Characters in a String using [RegEx] type accelerator

Powershell Tip: Escaping special Characters in a String using [RegEx] type accelerator

http://ift.tt/2pd2nnf

Often when using the -Replace (Operator) or .replace() (Method) in Powershell, I forget that the former parses the string in regex and in case you’re passing Special characters in the string, you’ll get the error all over your console.

That is because some special characters are part of regular expression language and are considered are Meta Characters in RegEx, so it’s always a best practice to escape special characters.

To your rescue,  here is a quick tip to escape all special characters in a string using the .Net Type accelerator for Regular expression [RegEx] , something like in the following image –

regex

and an animation with a use case.

tip

Hope you’ll find this useful, until next time, Cheers!

signature


Filed under: Powershell, PSTip, Today I Learned ! Tagged: .Net, dotnet, Powershell, PSTip, Regex, Regular expression, String, TypeAccelerator

Powershell

Powershell

via Planet PowerShell http://ift.tt/2ozzxeJ

April 23, 2017 at 10:51PM

PowerShell Server Inventory, Part 2: Collecting and Sending Data

PowerShell Server Inventory, Part 2: Collecting and Sending Data

http://ift.tt/2nMjgT8

This will be at least a 3 part series in which I will go over each aspect of my build which will cover the following topics:

Picking up where I left at, we have created our database and tables that will be used to store all of our data on our servers. Next up is to begin querying each server in the domain and pull as much information as we can and then send that data up to SQL so we can view it later on.

For this part of querying and sending data to the SQL server, I felt that it would be best to do some multithreading to speed up the time that it would take to hit all of the systems rather than perform the work sequentially. My multithreading approach of choice is to use my module, PoshRSJob to handle this. You can grab this module from my GitHub repo and place it on whichever server that you will be using to schedule the task from to perform the scans and uploads to SQL.

I say that you can scheduled a job to run the next script, but this is something that could be performed manually as well if need be and in fact, I would recommend this to ensure that no errors are thrown during the script execution.

The script that I wrote which will be used is called Invoke-ServerInventoryDataGathering.ps1 which only has a single parameter that accepts what SQL server will be used to send the data to. The script itself is over 1000 lines of which most of it consists of various regions which are used to group each type of data pull ranging from user account information to drive data to system configurations. In fact, about half of the code consists of helper functions which are used for data gathering or, in the case of Get-Server, is used to perform a domain wide lookup of all of the servers for a more dynamic approach of ensuring that all servers are being checked rather than using a file to host all of the systems.

image

There is a variable called $ServerGroup which is keeping a value of ‘MemberServer’. You can disregard this (keep it uncommented though) as I have my lab set up differently where Domain Admins do not have access to anything other than domain controllers which require a second script to be used to query the domain controllers and then write the data to the SQL server (which does allow domain admins to access for the purpose of writing the data to SQL).

Continuing to step through the code,  I have several regions which I use to group together each type of query. If more queries need to be added to grab more information, I can simply create a new region and add the necessary code to gather and send the data.

image

Each region follows the same type of process:

  1. Query the remote system for the particular information that I need
  2. If the query was successful, then proceed to perform a check against the database for data on the same system on the table that matches the type of data being queried for
  3. If data already exists, then remove the existing data
  4. Send the new data up to the appropriate table based on the current data being collected

Kicking off the script manually is just a matter of running it. In my case, VSQL is hard coded as the parameter value for the SQL server. In other cases, you would want to supply your own value.

image

I use Wait-RSJob with the –ShowProgress switch so I can track the status of each job. Each job represents a computer that is being scanned and the data gathered sent to a SQL database for later viewing.

image

Of course,  the preferred use of this script is by putting it into a scheduled task so it can be run on a daily schedule to handle all of the updates or new systems that come into the environment.

image

With all of this done, we can quickly verify that data actually exists. We can either look in SQL Server Management Studio for the data, or run a simple PowerShell command using the Invoke-SQL function against one of the tables.

image

PowerShell Example

$TSQL = @"
SELECT TOP 1000 [ComputerName]
      ,[Manufacturer]
      ,[Model]
      ,[SystemType]
      ,[SerialNumber]
      ,[ChassisType]
      ,[Description]
      ,[BIOSManufacturer]
      ,[BIOSName]
      ,[BIOSSerialNumber]
      ,[BIOSVersion]
      ,[InventoryDate]
  FROM [ServerInventory].[dbo].[tbGeneral]
"@

Invoke-SQLCmd -Computername VSQL -TSQL $TSQL -Database ServerInventory -CommandType Query

image

All of this code as well as the rest of the Server Inventory code is available at my GitHub repo here: http://ift.tt/2l6O03a

The last part in this series will take us through building a tool that will pull the data from SQL and provide users with a graphical way to view and generate reports.


Filed under: powershell Tagged: inventory, Powershell, server, SQL, tsql

Powershell

Powershell

via Planet PowerShell http://ift.tt/2ozzxeJ

April 24, 2017 at 12:59PM

Saturday, April 22, 2017

Determine the Default Password Policy for an Active Directory Domain with PowerShell

Determine the Default Password Policy for an Active Directory Domain with PowerShell

http://ift.tt/2pLEMIa

I’ve been working with PowerShell since the version 1.0 days and I’m still amazed that I find cmdlets that I didn’t know existed. Back in 2003, I had written some PowerShell code to query group policy for the lockout policy of an Active Directory domain. It used code similar to what’s shown in the following example which requires the GroupPolicy PowerShell module that installs as part of the RSAT (Remote Server Administration Tools).

I recently discovered that there’s a Get-ADDefaultDomainPasswordPolicy cmdlet that’s part of the ActiveDirectory PowerShell module that also installs as part of the RSAT.

You could select only the LockoutThreshold property to return the same results as shown in the first example:

The default lockout threshold for active directory accounts is 0 which means they’re never locked out. That’s not good so it’s something you might want to consider adding to your operational readiness testing for your infrastructure. The following example is a Pester test that checks this setting and verifies that it’s not set to zero.

Once you correct the problem by changing the account lockout threshold to a value greater than zero, the test should pass.

I like that Pester shows how long it took to execute the test. This tells me that using the Get-ADDefaultDomainPasswordPolicy is not only easier to use, but it’s also more efficient.

µ



Powershell

Powershell

via Planet PowerShell http://ift.tt/2ozzxeJ

April 21, 2017 at 02:42AM

Powershell: Remote install software

Powershell: Remote install software

http://ift.tt/2ogjGoc

I previously covered how to silently installing a MSI. The next thing an administrator wants to do is install it on a remote system. That is the logical next step. This isn’t always the easiest task for someone new to Powershell. Index Index Introduction Running installers remotely Installing from a...

Powershell

Powershell

via Planet PowerShell http://ift.tt/2ozzxeJ

April 22, 2017 at 06:47AM

Determine the Default Password Policy for an Active Directory Domain with PowerShell

Determine the Default Password Policy for an Active Directory Domain with PowerShell

http://ift.tt/2pLEMIa

I’ve been working with PowerShell since the version 1.0 days and I’m still amazed that I find cmdlets that I didn’t know existed. Back in 2003, I had written some PowerShell code to query group policy for the lockout policy of an Active Directory domain. It used code similar what’s shown in the following example which requires the GroupPolicy PowerShell module that installs as part of the RSAT (Remote Server Administration Tools).

I recently discovered that there’s a Get-ADDefaultDomainPasswordPolicy cmdlet that’s part of the ActiveDirectory PowerShell module that also installs as part of the RSAT.

You could select only the LockoutThreshold property to return the same results as shown in the first example:

The default lockout threshold for active directory accounts is 0 which means they’re never locked out. That’s not good so it’s something you might want to consider adding to your operational readiness testing for your infrastructure. The following example is a Pester test that checks this setting and verifies that it’s not set to zero.

Once you correct the problem by changing the account lockout threshold to a value greater than zero, the test should pass.

I like that Pester shows how long it took to execute the test. This tells me that using the Get-ADDefaultDomainPasswordPolicy is not only easier to use, but it’s also more efficient.

µ



Powershell

Powershell

via Mike F Robbins http://mikefrobbins.com

April 21, 2017 at 02:14AM

Friday, April 21, 2017

Check Backups

Check Backups

http://ift.tt/2p2zKco

Check for successful backups in NetWorker

Powershell

Powershell

via PowerShell Script Repository http://poshcode.org/

April 20, 2017 at 02:53PM

Thursday, April 20, 2017

PowerShell Studio 2017: Service Release v5.4.139

PowerShell Studio 2017: Service Release v5.4.139

http://ift.tt/2o8pFeL

Today we released a new build of PowerShell Studio 2017 (v5.4.139). In this build we added a number of new features, such as multi-language syntax coloring.

 

New Features

Multi-Language Syntax Coloring Support

Yes, you read that correctly, we added support for syntax coloring to a total of 19 languages / file types.

 

Does this mean PowerShell Studio is replacing PrimalScript?

No. The multi-language syntax coloring feature was only added for your convenience. The purpose of the feature is to allow viewing and basic editing of these files only. We realize often times users are required to work with a variety of file types; this is especially true when working with module projects.  Adding support for multi-language syntax coloring does not mean PowerShell Studio will allow you to compile or build projects using non-PowerShell languages. If you require that functionality, we recommend using PrimalScript instead.

 

Supported Languages:

Language Extensions
PowerShell *.ps1, *.psm1, *.psd1
Batch *.bat
C# *.cs
C++ *.cpp, *.h
C *.c
CSS *.css
HTML *.html, *.htm
INI *.ini, *.inf
Java *.java
JScript *.js
JSON *.json
PHP *.php
Python *.py
SQL *.sql
VB.NET *.vb
VB Script *.vbs
XAML *.xaml
XML *.xml, *.xsl, *.xslt, *.xsd
Text *.txt

 

The following are sample screenshots of different languages.

C#

CSharp

XML

XML

VB Script

VBScript

 

Editing Theme Coloring for Languages

You can edit the language specific coloring via Options->Editor->Font and Color:

Edit Language Coloring

When you save a color preset, it will include the settings for all the languages supported in PowerShell Studio.

 

Module Projects – Support for multiple psd1 and psm1 files

You can now add multiple psd1 and psm1 files to a module project as long as they are located in a sub-directory and not in the root of the project folder.

 

New Control Set

We added a new control set, TextBox – Browse for Folder (Modern) control set. This control uses the FolderBrowserModernDialog control instead of the traditional FolderBrowserDialog.

TextBox - Browse for Folder (Modern)

 

Console – Cancel Execution

We added a Cancel Execution command to the embedded console context menu, which tells the console to stop the execution of the running command.

Console - Cancel Execution

 

 

Please continue providing your feedback. Many of the new features included in the service builds are suggestions from users like you. Therefore, if you have any suggestions or feature requests, please share them with us on our Wish List and Feature Requests forum.



Powershell

Powershell

via SAPIEN Blog http://ift.tt/1oXyxzS

April 20, 2017 at 01:21AM

Saturday, April 15, 2017

Video: PowerShell Non-Monolithic Script Module Design

Video: PowerShell Non-Monolithic Script Module Design

http://ift.tt/2o13nZ2


This past Tuesday night, I presented a session on “PowerShell Non-Monolithic Script Module Design” for the Arizona PowerShell Users Group. The video from that presentation is now available.

The presentation begins at 10 minutes and 30 seconds seconds into the video.

The presentation materials to include the slide deck and code can be found in my presentations repository on GitHub.

µ



Powershell

Powershell

via Planet PowerShell http://ift.tt/2ozzxeJ

April 5, 2017 at 11:17PM

group policy powershell

group policy powershell

http://ift.tt/2oM2KVM

 

First install RSAT or run command on Windows Server 2008 R2 or later Domain Controller

http://ift.tt/1BvUr01

 

10982 Examples

image

image

 imageimage



Powershell

Powershell

via PowerShell 101 http://ift.tt/2pkXmJR

April 11, 2017 at 11:50PM

Add and remove lines in text based config files with PowerShell

Add and remove lines in text based config files with PowerShell

http://ift.tt/2oHSpee

Add and remove lines in text based config files with PowerShell

7. April 2017

blog.feldmann.io

PowerShell Version: >4 
Modules: none

I want to take some text file and look for a specific line, then delete that line and add some other line at the end of the file. Here’s how I made it work:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

# define file for input/output

$outfile = "C:\test.properties"

# set line to be removed

$old_content = 'install=1'

# set line to be added

$new_content = 'install=0'

 

#check if new line is already in place and then skip

$check = get-content $outfile | select-string -pattern $new_content

if (!$check)

    {

    #use temporary variable for enabling overwrite

    $tmp = get-content $outfile | select-string -pattern $old_content -notmatch

    $tmp | Set-Content $outfile

    # add new content at the end of the file

    $new_content | Out-File $outfile -Append ascii

    }



Powershell

Powershell

via Planet PowerShell http://ift.tt/2ozzxeJ

April 6, 2017 at 05:56PM

Test Active Directory User Accounts for a Default Password with PowerShell

Test Active Directory User Accounts for a Default Password with PowerShell

http://ift.tt/2px7Ban


#Requires -Version 3.0 -Modules ActiveDirectory

function Test-MrADUserPassword {

 

<#

.SYNOPSIS

    Test-MrADUserPassword is a function for testing an Active Directory user account for a specific password.

.DESCRIPTION

    Test-MrADUserPassword is an advanced function for testing one or more Active Directory user accounts for a

    specific password.

.PARAMETER UserName

    The username for the Active Directory user account.

 

.PARAMETER Password

    The password to test for.

 

.PARAMETER ComputerName

    A server or computer name that has PowerShell remoting enabled.

 

.PARAMETER InputObject

    Accepts the output of Get-ADUser.

 

.EXAMPLE

     Test-MrADUserPassword -UserName alan0 -Password Password1 -ComputerName Server01

 

.EXAMPLE

     'alan0'. 'andrew1', 'frank2' | Test-MrADUserPassword -Password Password1 -ComputerName Server01

 

.EXAMPLE

     Get-ADUser -Filter * -SearchBase 'OU=AdventureWorks Users,OU=Users,OU=Test,DC=mikefrobbins,DC=com' |

     Test-MrPassword -Password Password1 -ComputerName Server01

 

.INPUTS

    String, Microsoft.ActiveDirectory.Management.ADUser

.OUTPUTS

    PSCustomObject

.NOTES

    Author:  Mike F Robbins

    Website: http://mikefrobbins.com

    Twitter: @mikefrobbins

#>

 

    [CmdletBinding(DefaultParameterSetName='Parameter Set UserName')]

    param (

        [Parameter(Mandatory,

                   ValueFromPipeline,

                   ValueFromPipelineByPropertyName,

                   ParameterSetName='Parameter Set UserName')]

        [Alias('SamAccountName')]

        [string[]]$UserName,

 

        [Parameter(Mandatory)]

        [string]$Password,

 

        [Parameter(Mandatory)]

        [string]$ComputerName,

 

        [Parameter(ValueFromPipeline,

                   ParameterSetName='Parameter Set InputObject')]

        [Microsoft.ActiveDirectory.Management.ADUser]$InputObject

 

    )

    

    BEGIN {

        $Pass = ConvertTo-SecureString $Password -AsPlainText -Force

 

        $Params = @{

            ComputerName = $ComputerName

            ScriptBlock = {Get-Random | Out-Null}

            ErrorAction = 'SilentlyContinue'

            ErrorVariable  = 'Results'

        }

    }

 

    PROCESS {

        if ($PSBoundParameters.UserName) {

            Write-Verbose -Message 'Input received via the "UserName" parameter set.'

            $Users = $UserName

        }

        elseif ($PSBoundParameters.InputObject) {

            Write-Verbose -Message 'Input received via the "InputObject" parameter set.'

            $Users = $InputObject

        }

 

        foreach ($User in $Users) {    

            

            if (-not($Users.SamAccountName)) {

                Write-Verbose -Message "Querying Active Directory for UserName $($User)"

                $User = Get-ADUser -Identity $User

            }

    

            $Params.Credential = (New-Object System.Management.Automation.PSCredential ($($User.UserPrincipalName), $Pass))

 

            Invoke-Command @Params

 

            [pscustomobject]@{

                UserName = $User.SamAccountName

                PasswordCorrect =

                    switch ($Results.FullyQualifiedErrorId -replace ',.*$') {

                        LogonFailure {$false; break}

                        AccessDenied {$true; break}

                        default {$true}

                    }

            }    

    

        }

    

    }      

 

}



Powershell

Powershell

via Planet PowerShell http://ift.tt/2ozzxeJ

April 12, 2017 at 11:27PM

All-in-one Get-User cmdlet: Part 7 – Skype for Business Online

All-in-one Get-User cmdlet: Part 7 – Skype for Business Online

http://ift.tt/2odmUnJ

In the final part in this series (for now anyway), we will be adding Skype for Business Online support to the Get-MITUser cmdlet. The cmdlet will now offer a combined view of three objects: MsolUsers, Mailboxes, and CSOnlineUsers.

The previous design of the cmdlet heavily relies on (a) The fact we are only matching pairs of objects (MsolUsers and Mailboxes), and (b) The fact that Get-MsolUser only ever returns one result. Expanding this logic to include another object type is complicated.

There’s a Maths joke you can find floating around the internet (for example) that I think equally applies to code, particularly this scenario, that goes as follows:

A Mathematician and an Engineer attend a lecture by a Physicist. The topic concerns Kulza-Klein theories involving physical processes that occur in spaces with dimensions of 9, 12 and even higher. The Mathematician is sitting, clearly enjoying the lecture, while the Engineer is frowning and looking generally confused and puzzled. By the end the Engineer has a terrible headache. At the end, the Mathematician comments about the wonderful lecture. The Engineer says “How do you understand this stuff?”
Mathematician: “I just visualize the process”
Engineer: “How can you POSSIBLY visualize something that occurs in 9-dimensional space?”
Mathematician: “Easy, first visualize it in N-dimensional space, then let N go to 9”

In this case, to some extent we want to write code for N different types of user objects and then “let N go to 3”. What I have written is not quite generalised like that; but it’s nonetheless more modular and thus a lot simpler to create, read, and maintain; at the cost of possibly being less efficient. It will be a lot easier to expand in the future. (Get-SPOUser perhaps?).

Basically we go through each object type one-by-one (MsolUser, Mailbox, CSOnlineUser), try to obtain the object(s) from that Identity, exclude any we have already found in a previous step (using the ObjectId), and use the remaining object(s) to attempt to match with other object types.

It makes more sense in the revised flowchart:

The final flowchart for the Get-MITUser function.

First, we are going to modify our internal GetMsolUser2 and GetMailbox2 functions as follows:

  • We now return multiple MsolUsers/Mailboxes as separate objects, rather than a single object.
  • We include the ObjectId as part of the output object. This will simplify things as you will see later on. Note that Get-MsolUser returns the ObjectId as a Guid object, whereas Get-Mailbox returns it as a string.
    We will cast the string from Get-Mailbox to a Guid for consistency using the -as operator.

    Function GetMsolUser2 {
        [cmdletbinding()]
        Param(
            [string]
            $Identity
        )
        try {

            $MsolUser = $null
            $MsolUserError = $null
            $ObjectId = $null
 
            # Try converting $Identity to a Guid.
            # Note that Get-MsolUser attempts this exact process automatically
            # when called with the -ObjectId parameter.
            $Guid = $Identity -as [System.Guid]
 
            # If it can convert to a Guid, there's no way it's a UserPrincipalName,
            # if nothing else for the lack of @ sign.  So we only need to try one command.
            if ($Guid) {
                Write-Debug '[GetMsolUser2] Trying as GUID.'
                $MsolUser = Get-MsolUser -ObjectId $Guid -ErrorAction Stop
            } else {
                Write-Debug '[GetMsolUser2] Trying as UPN.'
                $MsolUser = Get-MsolUser -UserPrincipalName $Identity -ErrorAction Stop
            } #end else
     
            # Double check that we have actually obtained an MsolUser.
            if ($MsolUser) {
                Write-Debug '[GetMsolUser2] MsolUser found.'
                $MsolUserFound = $true
            } else {
                Write-Debug '[GetMsolUser2] MsolUser not found.'
                $MsolUserFound = $false
            } #end else
 
        } catch [Microsoft.Online.Administration.Automation.MicrosoftOnlineException] {
            Write-Debug '[GetMsolUser2] Get-MsolUser Error.'
            # We may need this message for later.    
            $MsolUserError = $_.Exception.Message
 
            if ($MsolUserError -match "^User Not Found.") {
                # We can handle a user not found, so no need to throw.
                $MsolUserFound = $false
            } else {
                # We don't (yet) know how to handle this error, so rethrow for the user to see.
                throw
            } #end else
 
        } #end catch [Microsoft.Online.Administration.Automation.MicrosoftOnlineException]
        
        if ($MsolUserFound) {
            # Output each found MsolUser.
            foreach ($ms in $MsolUser) {
                [pscustomobject]@{
                    MsolUser=$ms
                    MsolUserFound=$MsolUserFound
                    MsolUserError=$MsolUserError
                    Identity=$Identity
                    ObjectId=$ms.ObjectId
                }
            } #end foreach ($ms in $MsolUser)
        } else {
            # Output an single object with the error.
            [pscustomobject]@{
                MsolUser=$MsolUser
                MsolUserFound=$MsolUserFound
                MsolUserError=$MsolUserError
                Identity=$Identity
                ObjectId=$ObjectId
            }
        } #end else
    } #end Function GetMsolUser2

    Function GetMailbox2 {
        [cmdletbinding()]
        Param(
            [string]
            $Identity
        )
        try {

            $Mailbox = $null
            $MailboxError = $null
            $ObjectId = $null
    
            # Exchange cmdlets do not pay attention to the -ErrorAction parameter,
            # but they do pay attention to $ErrorActionPreference.
            # Capture the old $ErrorActionPreference first as we should set it back once done.
            $ErrorActionPreferenceOld = $ErrorActionPreference
            $global:ErrorActionPreference = 'Stop'
            Write-Debug "[GetMailbox2] `$global:ErrorActionPreference set to $global:ErrorActionPreference"
    
            $Mailbox = Get-Mailbox -Identity $Identity

            # Double check that we have actually obtained a Mailbox.
            if ($Mailbox) {
                Write-Debug '[GetMailbox2] Mailbox found.'
                $MailboxFound = $true
            } else {
                Write-Debug '[GetMailbox2] Mailbox not found.'
                $MailboxFound = $false
            } #end else
 
        } catch [System.Management.Automation.RemoteException] {
            Write-Debug '[GetMailbox2] Get-Mailbox error.'
            # We may need this message for later.    
            $MailboxError = $_.Exception.Message

            if ($MailboxError -match $MailboxErrorRegex) {
                # We can handle a mailbox not found, so no need to throw.
                $MailboxFound = $false
            } else {
                # We don't (yet) know how to handle this error, so rethrow for the user to see.
                throw
            } #end else
 
        } finally {

            # Set $ErrorActionPreference back to its previous value.
            $global:ErrorActionPreference = $ErrorActionPreferenceOld
            Write-Debug "[GetMailbox2] `$global:ErrorActionPreference set to $global:ErrorActionPreference"
        } #end finally

        if ($MailboxFound) {
            # Output each found Mailbox.
            foreach ($mbx in $Mailbox) {
                [pscustomobject]@{
                    Mailbox=$mbx
                    MailboxFound=$MailboxFound
                    MailboxError=$MailboxError
                    Identity=$Identity
                    # Exchange outputs the ObjectId as a string, so we cast as Guid
                    ObjectId=$mbx.ExternalDirectoryObjectId -as [Guid]
                }
            } #end foreach ($mbx in $Mailbox)
        } else {
            # Output a single object with the error.
            [pscustomobject]@{
                Mailbox=$Mailbox
                MailboxFound=$MailboxFound
                MailboxError=$MailboxError
                Identity=$Identity
                ObjectId=$null
            }
        } #end else

    } #end Function GetMailbox2

Then we create our GetCSOnlineUser2 internal function in the same manner as GetMsolUser2 and GetMailbox2. It’s most similar to GetMailbox2, asides from the ObjectId doesn’t need to be cast to a Guid.


$CSOnlineUserErrorRegex = @'
^Management object not found for identity ".*"\.$
'@

    Function GetCSOnlineUser2 {
        [cmdletbinding()]
        Param(
            [string]
            $Identity
        )
        try {

            $CSOnlineUser = $null
            $CSOnlineUserError = $null
            $ObjectId = $null
    
            # Capture the old $ErrorActionPreference first as we should set it back once done.
            $ErrorActionPreferenceOld = $ErrorActionPreference
            $global:ErrorActionPreference = 'Stop'
            Write-Debug "[GetCSOnlineUser2] `$global:ErrorActionPreference set to $global:ErrorActionPreference"
    
            $CSOnlineUser = Get-CSOnlineUser -Identity $Identity

            # Double check that we have actually obtained a CSOnlineUser.
            if ($CSOnlineUser) {
                Write-Debug '[GetCSOnlineUser2] CSOnlineUser found.'
                $CSOnlineUserFound = $true
                $ObjectId = $CSOnlineUser.ObjectId
            } else {
                Write-Debug '[GetCSOnlineUser2] CSOnlineUser not found.'
                $CSOnlineUserFound = $false
            } #end else
 
        } catch [System.Management.Automation.RemoteException] {
            Write-Debug '[GetCSOnlineUser2] Get-CSOnlineUser error.'
            # We may need this message for later.    
            $CSOnlineUserError = $_.Exception.Message

            if ($CSOnlineUserError -match $CSOnlineUserErrorRegex) {
                # We can handle a CSOnlineUser not found, so no need to throw.
                $CSOnlineUserFound = $false
            } else {
                # We don't (yet) know how to handle this error, so rethrow for the user to see.
                throw
            } #end else
 
        } finally {

            # Set $ErrorActionPreference back to its previous value.
            $global:ErrorActionPreference = $ErrorActionPreferenceOld
            Write-Debug "[GetCSOnlineUser2] `$global:ErrorActionPreference set to $global:ErrorActionPreference"
        } #end finally

        [pscustomobject]@{
            CSOnlineUser=$CSOnlineUser
            CSOnlineUserFound=$CSOnlineUserFound
            CSOnlineUserError=$CSOnlineUserError
            ObjectId=$ObjectId
        }
    } #end Function GetCSOnlineUser2

Then we modify our NewMITUserObj internal function:

  1. We add support for the CSOnlineUser object – take it as input, output its properties.
  2. I also redo how the individual properties are set – rather than grouping by preferred object source, group by individual variable instead.
  3. Additionally, to avoid repetition in the various stages of Get-MITUser, I move the logic to obtain MailboxStatistics (if requested) into this function.

$MailboxStatisticsEmptyObj = [pscustomobject]@{
    MailboxStatistics=$null
    MailboxStatisticsFound=$false
    MailboxStatisticsError=$null
} #end [pscustomobject]

Function NewMITUserObj {
        [cmdletbinding()]
        Param(
            [Parameter(mandatory=$true)]
            $MsolObj,

            [Parameter(mandatory=$true)]
            $MailboxObj,

            [Parameter(mandatory=$true)]
            $CSOnlineUserObj,

            [switch]
            $IncludeMailboxStatistics=$false
        )



        $DisplayName =            if ($MsolObj.MsolUserFound)             { $MsolObj.MsolUser.DisplayName }
                              elseif ($MailboxObj.MailboxFound)           { $MailboxObj.Mailbox.DisplayName }
                              elseif ($CSOnlineUserObj.CSOnlineUserFound) { $CSOnlineUserObj.CSOnlineUser.DisplayName }

        $UserPrincipalName =      if ($MsolObj.MsolUserFound)             { $MsolObj.MsolUser.UserPrincipalName }
                              elseif ($MailboxObj.MailboxFound)           { $MailboxObj.Mailbox.UserPrincipalName }
                              elseif ($CSOnlineUserObj.CSOnlineUserFound) { $CSOnlineUserObj.CSOnlineUser.UserPrincipalName }

        $ObjectId =               if ($MsolObj.MsolUserFound)             { $MsolObj.ObjectId }
                              elseif ($MailboxObj.MailboxFound)           { $MailboxObj.ObjectId }
                              elseif ($CSOnlineUserObj.CSOnlineUserFound) { $CSOnlineUserObj.ObjectId }
        
        # For the PrimarySMTPAddress we prefer Exchange over Skype for Business over Office 365
        $PrimarySmtpAddress =     if ($MailboxObj.MailboxFound)           { $MailboxObj.Mailbox.PrimarySmtpAddress }
                              elseif ($CSOnlineUserObj.CSOnlineUserFound) { $CSOnlineUserObj.CSOnlineUser.ProxyAddresses | ForEach-Object { if ($_ -cmatch '^SMTP:(.*)$') { $Matches[1] } } }
                              elseif ($MsolObj.MsolUserFound)             { $MsolObj.MsolUser.ProxyAddresses | ForEach-Object { if ($_ -cmatch '^SMTP:(.*)$') { $Matches[1] } } }

        
        $obj = [pscustomobject]@{
            DisplayName        = $DisplayName
            UserPrincipalName  = $UserPrincipalName
            PrimarySmtpAddress = $PrimarySmtpAddress
            ObjectId           = $ObjectId
            MsolUser           = $MsolObj.MsolUser
            MsolUserFound      = $MsolObj.MsolUserFound
            MsolUserError      = $MsolObj.MsolUserError
            Mailbox            = $MailboxObj.Mailbox
            MailboxFound       = $MailboxObj.MailboxFound
            MailboxError       = $MailboxObj.MailboxError
            CSOnlineUser       = $CSOnlineUserObj.CSOnlineUser
            CSOnlineUserFound  = $CSOnlineUserObj.CSOnlineUserFound
            CSOnlineUserError  = $CSOnlineUserObj.CSOnlineUserError
        } #end [pscustomobject]

        if ($IncludeMailboxStatistics) {
            $TypeName = 'MilneIT.MITUser.UserWithMailboxStatistics'

            if ($MailboxObj.MailboxFound) {
                # If the mailbox is found, use the Mailbox Guid to obtain the MailboxStatistics.
                $MailboxStatisticsObj = GetMailboxStatistics2 -Identity $MailboxObj.Mailbox.Guid.Guid
            } else {
                # If the mailbox isn't found, use the empty object instead.
                $MailboxStatisticsObj = $MailboxStatisticsEmptyObj
            } #end else
        
            # Additional properties regardless of whether we found the Mailbox Statistics or not.
            $additional = [ordered]@{
                MailboxStatistics      = $MailboxStatisticsObj.MailboxStatistics
                MailboxStatisticsFound = $MailboxStatisticsObj.MailboxStatisticsFound
                MailboxStatisticsError = $MailboxStatisticsObj.MailboxStatisticsError
            } #end [ordered]

            # Additional properties dependent on whether we found the mailbox statistics or not.
            # The somewhat roundabout way of doing it is so we do not have an error with StrictMode.

            if ($MailboxStatisticsObj.MailboxStatisticsFound) {
                $additional2 = [ordered]@{
                    ItemCount     = $MailboxStatisticsObj.MailboxStatistics.ItemCount
                    TotalItemSize = $MailboxStatisticsObj.MailboxStatistics.TotalItemSize.Value
                } #end [ordered]
            } else {
                $additional2 = [ordered]@{
                    ItemCount     = $null
                    TotalItemSize = $null
                } #end [ordered]
            } #end else

            $obj | Add-Member -NotePropertyMembers $additional
            $obj | Add-Member -NotePropertyMembers $additional2

        } else {
            $TypeName = 'MilneIT.MITUser.User'
        } #end else

        $obj.PSObject.TypeNames.Insert(0,$TypeName)

        $obj

    } #end Function NewMITUserObj

Now we put this together in our reworked Get-MITUser cmdlet. Just like in the flowchart:

  1. It tries to find MsolUsers from the identity, and then gets Mailboxes and CSOnlineUsers from that.
  2. It tries to find Mailboxes from the identity, excludes anything we have already found (by ObjectId), and then finds the MsolUsers and CSOnlineUsers from those.
  3. It tries to find CSOnlineUsers from the identity, excludes anything we have already found (by ObjectId), and then find the MsolUsers and Mailboxes from those.
  4. It then spits it all out as output.

While we’re at it, I also add support for receiving multiple objects and input from the pipeline in true Microsoft cmdlet style. This required a few things:

  1. For the Identity parameter, setting ValueFromPipeline to $true.
  2. For the Identity parameter, changing its type from [string] to an array of strings [string[]].
  3. Wrapping the main logic in a foreach loop (so it runs once for each object specified as a parameter).
  4. Wrapping that foreach loop in a Process block (so it runs once for each object passed through the pipeline).

$MITUserErrorText = @'
Unable to find user {0}.
MsolUserError:     {1}
MailboxError:      {2}
CSOnlineUserError: {3}
'@

    Function Get-MITUser {
        <#
        .SYNOPSIS
        Obtains an MITUser object which combines Office 365 MsolUser, Exchange Online Mailbox, and CSOnlineUser.


        .PARAMETER Identity
        The Identity of the user you wish to obtain.  You can use any of the following:

        From Get-MsolUser:
            - UserPrincipalName
            - ObjectId

        From Get-Mailbox:
            - Name
            - Display name
            - Alias
            - Distinguished name (DN)
            - Canonical DN
            - <domain name>\<account name>
            - Email address
            - GUID
            - LegacyExchangeDN
            - SamAccountName
            - User ID or user principal name (UPN)

        From Get-CSOnlineUser:
            - SIP Address
            - UserPrincipalName
            - <domain name>\<account name>
            - Display name
            - Distinguished name (DN)

        .PARAMETER IncludeMailboxStatistics
        Whether to include MailboxStatistics properties.
        This will increase the time the cmdlet takes per user.
        
        #>
        [cmdletbinding()]
        Param(
            [Parameter(
                mandatory=$true,
                ValueFromPipeline=$true
            )]
            [string[]]
            $Identity,

            [switch]
            $IncludeMailboxStatistics=$false
        )

        Process {

            foreach ($id in $Identity) {

                # An empty array we will use to store and then output all combined objects.
                $MITUsers = @()

                ###
                # MsolUser
                ###

                # Try to get the MsolUser from the provided identity.
                $MsolObjects = GetMsolUser2 -Identity $id

                # At present only one object should be returned, but for the sake of future proofing 
                # we will allow for GetMsolUser2 to give us multiple objects.

                # Obtain Mailbox and CSOnlineUser from MsolUser.
                foreach ($MsolObj in ($MsolObjects | Where-Object MsolUserFound) ) {
                    $MailboxObj      = GetMailbox2      -Identity $MsolObj.ObjectId
                    $CSOnlineUserObj = GetCSOnlineUser2 -Identity $MsolObj.ObjectId

                    Write-Debug ('[Get-MITUser] MsolUser: {0}.' -F $MsolObj.ObjectId)
                    Write-Debug ('[Get-MITUser]      MailboxFound:       {0}.' -F $MailboxObj.MailboxFound)
                    Write-Debug ('[Get-MITUser]      CSOnlineUserFound:  {0}.' -F $CSOnlineUserObj.CSOnlineUserFound)

                    $MITUsers += NewMITUserObj -MsolObj $MsolObj -MailboxObj $MailboxObj -CSOnlineUserObj $CSOnlineUserObj -IncludeMailboxStatistics:$IncludeMailboxStatistics

                } #end foreach ($MsolObj in $MsolObjects)

                ###
                # Mailbox
                ###

                # Try to get the Mailbox from the provided identity.
                $MailboxObjects = GetMailbox2 -Identity $id

                # Filter out any we already have.
                if ($MITUsers) {
                    $MailboxObjects = $MailboxObjects | Where-Object { $_.ObjectId -notin $MITUsers.ObjectId }
                } #end if ($MITUsers)

                # Obtain MsolUser and CSOnlineUser from Mailbox.
                foreach ($MailboxObj in ($MailboxObjects | Where-Object MailboxFound) ) {
            
                    $MsolObj         = GetMsolUser2     -Identity $MailboxObj.ObjectId
                    $CSOnlineUserObj = GetCSOnlineUser2 -Identity $MailboxObj.ObjectId

                    Write-Debug ('[Get-MITUser] Mailbox: {0}.' -F $MailboxObj.ObjectId)
                    Write-Debug ('[Get-MITUser]      MsolUserFound:      {0}.' -F $MsolObj.MsolUserFound)
                    Write-Debug ('[Get-MITUser]      CSOnlineUserFound:  {0}.' -F $CSOnlineUserObj.CSOnlineUserFound)

                    $MITUsers += NewMITUserObj -MsolObj $MsolObj -MailboxObj $MailboxObj -CSOnlineUserObj $CSOnlineUserObj -IncludeMailboxStatistics:$IncludeMailboxStatistics
                }

                ###
                # CSOnlineUser
                ###

                # Try to get the CSOnlineUser from the provided identity.
                $CSOnlineUserObjects = GetCSOnlineUser2 -Identity $id

                # Filter out any we already have.
                if ($MITUsers) {
                    $CSOnlineUserObjects = $CSOnlineUserObjects | Where-Object { $_.ObjectId -notin $MITUsers.ObjectId }
                } #end if ($MITUsers)

                # Obtain MsolUser and Mailbox from CSOnlineUser.
                foreach ($CSOnlineUserObj in ($CSOnlineUserObjects | Where-Object CSOnlineUserFound) ) {
                    $MsolObj         = GetMsolUser2     -Identity $CSOnlineUserObj.ObjectId
                    $MailboxObj      = GetMailbox2      -Identity $CSOnlineUserObj.ObjectId

                    Write-Debug ('[Get-MITUser] CSOnlineUser: {0}.' -F $CSOnlineUserObj.ObjectId)
                    Write-Debug ('[Get-MITUser]      MsolUserFound:      {0}.' -F $MsolObj.MsolUserFound)
                    Write-Debug ('[Get-MITUser]      MailboxFound:       {0}.' -F $MailboxObj.MailboxFound)

                    $MITUsers += NewMITUserObj -MsolObj $MsolObj -MailboxObj $MailboxObj -CSOnlineUserObj $CSOnlineUserObj -IncludeMailboxStatistics:$IncludeMailboxStatistics
                }

                ###
                # Finish up
                ###

                # If our array is still empty, then we should throw.
                if (-not $MITUsers) {
                    Write-Debug '[Get-MITUser] Neither MsolUser nor Mailbox found.'
                    # Note that if we are here, we know that $(Msol|Mailbox|CsOnlineUser)Objects
                    # contains exactly one object.  So we don't need to handle arrays at all.
                    # This object basically says nothing is found and gives us the error.
                    $ErrorText = $MITUserErrorText -F $id,$MsolObjects.MsolUserError,$MailboxObjects.MailboxError,$CSOnlineUserObjects.CSOnlineUserError
                    throw ($ErrorText)
                }
                
                # Output all users found.
                $MITUsers

            } #end foreach ($id in $Identity)

        } #end Process

    } #end Function Get-MITUser

One final change – remember our cmdlet formatting in MilneIT.format.ps1xml? If we don’t make changes here, our objects will include new properties but they won’t be displayed by default. All we need to do is add the CSOnlineUserFound property to each object type:


...
                            <TableColumnItem>
                                <PropertyName>MailboxFound</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>CSOnlineUserFound</PropertyName>
                            </TableColumnItem>
...

Now let’s run a few tests. Remember our sample users from Part 5? Nothing has changed since then, and they are all currently enabled for Skype for Business Online.

Let’s set our $DebugPreference to Continue so that debug messages are displayed but the command isn’t paused. Then let’s begin with our simple example Vicki.


PS> $DebugPreference = 'Continue'

PS> Get-MITUser -Identity 'Vicki@milneitlab01.onmicrosoft.com'
DEBUG: [Get-MITUser] MsolUser: fe491646-d83c-4043-a6a5-d40b0a3075eb.
DEBUG: [Get-MITUser]      MailboxFound:       True.
DEBUG: [Get-MITUser]      CSOnlineUserFound:  True.

DisplayName UserPrincipalName                  PrimarySmtpAddress                 MsolUserFound MailboxFound CSOnlineUserFound
----------- -----------------                  ------------------                 ------------- ------------ -----------------
Vicki Marsh Vicki@milneitlab01.onmicrosoft.com Vicki@milneitlab01.onmicrosoft.com True          True         True             


PS> Get-MITUser -Identity 'Vicki'
DEBUG: [Get-MITUser] Mailbox: fe491646-d83c-4043-a6a5-d40b0a3075eb.
DEBUG: [Get-MITUser]      MsolUserFound:      True.
DEBUG: [Get-MITUser]      CSOnlineUserFound:  True.

DisplayName UserPrincipalName                  PrimarySmtpAddress                 MsolUserFound MailboxFound CSOnlineUserFound
----------- -----------------                  ------------------                 ------------- ------------ -----------------
Vicki Marsh Vicki@milneitlab01.onmicrosoft.com Vicki@milneitlab01.onmicrosoft.com True          True         True     


PS> Get-MITUser -Identity 'SIP:Vicki@milneitlab01.onmicrosoft.com'
DEBUG: [Get-MITUser] CSOnlineUser: fe491646-d83c-4043-a6a5-d40b0a3075eb.
DEBUG: [Get-MITUser]      MsolUserFound:      True.
DEBUG: [Get-MITUser]      MailboxFound:       True.

DisplayName UserPrincipalName                  PrimarySmtpAddress                 MsolUserFound MailboxFound CSOnlineUserFound
----------- -----------------                  ------------------                 ------------- ------------ -----------------
Vicki Marsh Vicki@milneitlab01.onmicrosoft.com Vicki@milneitlab01.onmicrosoft.com True          True         True  

We can see that in all cases, the output object is same. However the Debug output shows us that in the first case, it was first matched on MsolUser (by UserPrincipalName), in the second case it was first matched on the Mailbox (by Alias), and in the third case it was first matched on the CSOnlineUser (by the SIP address).

How does it handle the two Wills? Also let’s pass this from the pipeline this time.


PS> 'Will' | Get-MITUser
DEBUG: [Get-MITUser] Mailbox: 709f2ce8-4cbd-4f5d-b0b6-dc56f337c753.
DEBUG: [Get-MITUser]      MsolUserFound:      True.
DEBUG: [Get-MITUser]      CSOnlineUserFound:  True.
DEBUG: [Get-MITUser] CSOnlineUser: d5bffab9-3855-44d2-82ab-4b75ba62e67a.
DEBUG: [Get-MITUser]      MsolUserFound:      True.
DEBUG: [Get-MITUser]      MailboxFound:       False.

DisplayName  UserPrincipalName                  PrimarySmtpAddress                MsolUserFound MailboxFound CSOnlineUserFound
-----------  -----------------                  ------------------                ------------- ------------ -----------------
Will Cook    Will2@milneitlab01.onmicrosoft.com Will@milneitlab01.onmicrosoft.com True          True         True             
Will Richard Will@milneitlab01.onmicrosoft.com                                    True          False        True             

This is good, we can now get both of them from just their first name. Will Cook is first matched by Mailbox, by his alias ‘Will’. Will Richard is matched by the CSOnlineUser. Interestingly this doesn’t match anything that Get-CSOnlineUser officially matches – the exact string “Will” is not his SIP address, UPN, domain\username, Display Name, or Distinguished Name.

Without further investigation, my best guess is that Get-CSOnlineUser also matches Aliases, which is a property of CSOnlineUser as you can see below. The only other exactly equal match is his First Name, which is unlikely to be used in Get-CSOnlineUser’s matching process.


PS> $WillR = Get-MITUser 'Will Richard'

PS> $WillR.CSOnlineUser | Format-Table DisplayName,Alias,FirstName,LastName,ObjectId

DisplayName  Alias FirstName LastName ObjectId                            
-----------  ----- --------- -------- --------                            
Will Richard Will  Will      Richard  d5bffab9-3855-44d2-82ab-4b75ba62e67a

And finally, how does it look now when someone doesn’t exist?


PS> Get-MITUser -Identity 'DoesNotExist'
DEBUG: [Get-MITUser] Neither MsolUser nor Mailbox found.
Unable to find user DoesNotExist.
MsolUserError:     User Not Found.  User: DoesNotExist.
MailboxError:      The operation couldn't be performed because object 'DoesNotExist' couldn't be found on 'MMXP123A001DC05.GBRP123A001.PROD.OUTLOOK.COM'.
CSOnlineUserError: Management object not found for identity "DoesNotExist".
At C:\Users\Ryan\OneDrive\Scripts\Milne.IT\PowerShell\Modules\milneit\MilneIT.psm1:830 char:21
+                     throw ($ErrorText)
+                     ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (Unable to find ..."DoesNotExist".:String) [], RuntimeException
    + FullyQualifiedErrorId : Unable to find user DoesNotExist.
MsolUserError:     User Not Found.  User: DoesNotExist.
MailboxError:      The operation couldn't be performed because object 'DoesNotExist' couldn't be found on 'MMXP123A001DC05.GBRP123A001.PROD.OUTLOOK.COM'.
CSOnlineUserError: Management object not found for identity "DoesNotExist".

Good, it shows us all three errors, which makes for easier troubleshooting.

The full code as of this blog post can be viewed on Bitbucket here. You can always view the latest version of the full repository here.



Powershell

Powershell

via Planet PowerShell http://ift.tt/2ozzxeJ

April 13, 2017 at 09:28AM