Monday, November 30, 2020

Demo of Azure AD Administrative Units

I recently learned about Administrative Units (AU) in Azure AD. So before long, I wanted to manipulate them in PowerShell. I found the documentation somewhat lacking. Here's a cheat sheet that I hope benefits you as much as it does me. 

First you will need to install the AzureAD module from the PowerShell Gallery.

#have to connect to your environment
Connect-AzureAD

#return all AUs: properties of displayname, id, and description
Get-AzureADMSAdministrativeUnit

#get an AU by name (returns nothing if no matches)
Get-AzureADMSAdministrativeUnit -Filter "displayname eq 'lowe'"
#not sure how to do a "like" or "contains" query
#create new AU with a displayname and description
New-AzureADMSAdministrativeUnit -Description "146" -DisplayName "Lowe"
#it takes a few minutes for changes to show up in the Web UI

#get the members of an AU as users (with DisplayName and UPN)
Get-AzureADMSAdministrativeUnitMember -Id 71084ab0-34c8-4388-9793-21e7a9776f9c | foreach-object {Get-AzureADUser -ObjectId $_.id}

#add a member to an AU (each object has an ID property)
Add-AzureADMSAdministrativeUnitMember -Id 2518ab7d-6447-4824-88c2-94cc4bc4a75f -RefObjectId 009133a3-b732-4150-aa04-ca459f6027a1

#how to remove a member (without knowing the IDs)
$manualAU = Get-AzureADMSAdministrativeUnit -Filter "displayname eq 'manual'"
$johnny = Get-AzureADUser -ObjectId johnny@demo.onmicrosoft.com
Remove-AzureADMSAdministrativeUnitMember -Id $manualAU.Id -MemberId $johnny.ObjectId

#change displayname or description
Set-AzureADMSAdministrativeUnit -Id 36db27e3-b094-4813-a899-de7cadebf704 -Description "delete this one" -DisplayName "TBD"

#delete an AU (it will not ask you to confirm)
Remove-AzureADMSAdministrativeUnit -Id 36db27e3-b094-4813-a899-de7cadebf704

Unfortunately, none of these have WhatIf or Confirm, and I haven't seen any Verbose or Debug output for them. 

(If you're new to PowerShell, let me remind you that you don't have to quote your parameter values if they don't have spaces; you can see sometimes I did, and sometimes I didn't.)

Please let me know if you have any questions! Thanks for reading!


Thursday, October 15, 2015

Modify DNS Entry

I learned today that it is a multistep process to use PowerShell to change the IP address of an A record in DNS. I think PowerShell should work in a flow with cmdlets, but there are some things, like this scenario, that don't fit right.

Here's the example:
$old = Get-DnsServerResourceRecord -Name mdm -ComputerName DNS1 -ZoneName contoso.com
$new = $old.clone() #so that there are two copies of the DNS record object
$new.RecordData.IPv4Address = [ipaddress] "10.11.12.13" #stores as System.Net.IPAddress object
Set-DnsServerResourceRecord -OldInputObject $old -NewInputObject $new -ComputerName DNS1 -ZoneName contoso.com #basically swapping old and new
#verify with:
Get-DnsServerResourceRecord -Name mdm -ComputerName DNS1 -ZoneName contoso.com

Some PowerShell is straightforward, and some takes a few steps to get a result.  Maybe you can write your own function that performs the same action (update IP address) in one line!  Try it!

Friday, August 14, 2015

Reorganize My Profile

As you may have guessed, I am testing a new PowerShell profile.  My old one had been around for a few years and was based in my knowledge and PowerShell's features at the time.  Here are the principles I am using for my new profile:
  • I'm organizing the functions I most often use into modules and leaving out special-purpose scripts. I'm putting all organization-related items in one module and trying to generalize all the other modules for sharing/publishing purposes. This includes using environment variables like $env:USERDNSDOMAIN
  • Instead of import statements or complicated ways to check for modules or elevated prompt, I am using Requires statements on each file (only one per file is allowed).  For more info, use  Get-Help about_requires
  • Since PowerShell 3, modules are loaded on-demand.  Any modules that show up in Get-Module -ListAvailable are included in this.  If a script or module requires a module that is available, then that module is imported automatically. 
  • To add a module to -ListAvailable, create a .PSM1 file and put it in Documents\WindowsPowerShell\Modules\modname, and the folder and PSM1 file names have to match.  I'm moving my modules here in this format.
  • Instead of typing the long path to my WindowsPowerShell folder, I created a PSDrive (map) called my:
    $null = New-PSDrive -Name my -Root (Split-Path -Path $PROFILE -Parent) -PSProvider FileSystem

So how do you organize your scripts? Leave a comment below!

Thursday, August 6, 2015

Test Profile

Want to test your new profile script without loading your existing one(s)?  Run this from the Run command:
powershell.exe /noexit /noprofile /file myfile.ps1

Here are some other parameters for powershell.exe
Find out more about profiles here.

Parameter Validation

To summarize Glenn Sizemore from the Scripting Guys blog, here is how you can validate your PowerShell parameters when you write your own script (requires V2):
Param(
[ValidateSet("Department", "AppData")][string]$type,
[ValidateRange(10, 25)][int]$size,
[ValidateScript({Test-connection $_ -count 2 -quiet})][string]$computername
)

And since version 3, the PowerShell ISE is smart enough (via Intellisense) to show you your choices in a drop-down for a ValidateSet parameter. 

Thursday, July 9, 2015

Rounding Numbers

If you want to round numbers in PowerShell, then you have two choices (at least) with slightly different results.

Let's set the stage:
PS > $a = 8
PS > $b = 6
PS > $a / $b
1.3333333333333

One way is to use the format string approach.
 
PS > "{0:n2}" -f ($a / $b)
1.33

So "{0:n2}" denotes a formatted string, where the 2 is how many decimal places to show.  After the -f, you put your expression to format.  There are other choices for this formatted string, which I will not detail here.  The point is that your output is a string.

Another way is to use the Math .NET class to Round.
PS > [Math]::Round(($a / $b), 2)
1.33

This time, however, you get a Double instead of a string. 

It looks like PowerShell does a great job at sorting number strings as numbers.  So far, it doesn't matter which way you use above; Sort-Object puts the numbers in the right order.  PowerShell even lets you do Math operations on numbers stored as strings!  Isn't it great?

Wednesday, June 3, 2015

New Hyper-V Virtual Machine with PowerShell

Here is the PowerShell for a new VM like the wizard does:
$vmName = "New Virtual Machine"
$vmPath = "C:\ProgramData\Microsoft\Windows\Hyper-V\"
New-VM -Name $vmName -Path "$vmPath\$vmName" -MemoryStartupBytes 512MB -NewVHDPath ` "$vmPath\$vmName\$vmName.vhdx" -NewVHDSizeBytes 127gb -Generation 1 -BootDevice CD
#new Gen 1 VM with files stored in $vmPath, with 512MB static RAM, with 1 CPU, with 127GB dynamic VHDX, #disconnected from network, with empty CD drive

Here is how to make other common configurations (one line at a time):
Set-VMDvdDrive -VMName $vmName -Path "k:\ISO\ISO.iso"; #add ISO file to existing drive
Set-VM -NewVMName $vmName -ProcessorCount 2 -DynamicMemory -MemoryMaximumBytes 4096MB #set dynamic memory and CPU
Set-VMNetworkAdapter -VMName $vmName -Name "SecretNet" #connect to network
Set-VM -NewVMName $vmName -AutomaticStartAction StartIfRunning -AutomaticStopAction ShutDown #change auto actions
Checkpoint-VM -Name $vmName #make a new snapshot/checkpoint

Friday, May 22, 2015

PowerShell Random Sort a List

Maybe you have a list that you want to put in a random order.  I had a list of words that I wanted shuffled without repeating.  At first, I was thinking that I'd have to devise some way of choosing at random without replacement, so that Get-Random wouldn't choose the same word twice.

Then I found that Get-Random has a -Count parameter, so I thought: could I make the count the same as the number of items on my list?

Of course!  Why did I even question?
"Mercury" , "Venus" , "Earth" , "Mars" , "Jupiter" , "Saturn" , "Uranus" , "Neptune" , "Pluto" | Get-Random  -Count  9

However, I did learn that the count cannot exceed the length of the list.  If it does, PowerShell won't complain; it will just stop when it has listed every item once. 

Get-Date -format

I used to do it the long way, gathering the minute, hour or the day, month, year.  Then I learned about -format, and it changed everything and made the code much shorter.

Get-Date -Format "yyyyMMdd"
20150522
Get-Date -Format "MM-dd-yy"
05-22-15

See: http://technet.microsoft.com/en-us/library/ee692801.aspx

Tuesday, September 18, 2012

Failing-over in a Cluster

I was afraid to try PowerShell on a Failover Cluster, but today I finally did, and it worked very well. I needed to move a DFS from one node to another. So I opened PowerShell As Administrator, imported the module "FailoverClusters" and ran the command: 
Move-ClusterGroup -Name staff -Node filesnode1 
It worked perfectly! The cmdlet didn't return until the DFS was back online, and the returned object said it was online. Easy!

Monday, August 6, 2012

Get Installed Programs

I just learned about the WMI class "win32_product", which shows much of the same info as "Add/Remove Programs".  It works like any other WMI class.  I will use it to find the version of a program installed on several dozen machines and whether any are out-of-date, like this:

Get-WMIObject win32_product -Filter "name='$programName'" -computername $comps | Select-Object __Server,identifyingnumber, name, version | Group-object version

Visit this Technet page for more info on using WMI classes: http://technet.microsoft.com/en-us/library/ee176860.aspx

Tuesday, July 31, 2012

What If Mode - Adding WhatIf to Scripts

What if mode is one of my favorite features of PowerShell.  No other language I have used has given me this ability, and it is very exciting and useful.  You probably already know that -WhatIf will describe the action that would take place rather than actually doing it.  How then can one use it in a script?  It takes a few steps.


First, add the code [CmdletBinding(SupportsShouldProcess=$true)] immediately before the Param statement. This unlocks -Whatif and other common parameters.  Next, wherever you are going to perform an operation that would change something, such as to move an AD account, enclose the commands(s) in an if statement with a condition like this:

if ($PScmdlet.ShouldProcess("Move Account $($child.cn.toString()) to OU $($disabledOU.ou.toString())","","")) {
    $child.MoveTo($disabledOU)
}


The $PSCmdlet.ShouldProcess() method takes many arguments, but the one I use the most takes first a string describing the action and the object, followed by two empty strings.  The execution of this method will respond appropriately based on whether WhatIf is on or not.


There is a lot of potential here, and I encourage you to explore it!

Monday, July 30, 2012

Set Boolean Switches

I was excited when I read in the Get-Help about_commonParameters article that one can set true or false on a switch parameter.  Previously, I thought that it was false unless specified.  Now I see that if one adds a colon and a Boolean variable, then the switch can be set to false even when specified.  It looks like this:

restart-computer -whatif:$false

There were several cases when I didn't know whether I would need a switch or not, and I had convoluted code to form an expression which I would then invoke.  Now it is much cleaner.

So reading those long about_* articles does have its benefits!

Thursday, July 26, 2012

Creating Custom Objects

PowerShell is all about being object-oriented.  Once I learned how to create custom objects, then I really started having fun.  One can add custom properties to either a "blank" object or an existing object (which is cooler). 


To do so, either start with the existing object or a new-object system.object.  Then pipe it to add-member.  Add-member takes a -membertype, then the -name of the property, and finally  the -value.  


The top-three types that I use are as follows: 

  • noteproperty for a "static" value; the value is whatever, can be in parentheses
  • scriptproperty for a "dynamic" value, a code block; use $this. to reference the object
  • aliasproperty for another name for an existing property; the value is just the name of the existing property

I have had much success with add-member in my scripting. I cannot explain it all here, but I hope it whets your appetite!  Remember to use get-help add-member -detailed.

Monday, July 23, 2012

Generating Terminating and non-Terminating Errors

Maybe you know that PowerShell has two kinds of errors: terminating and non-terminating.   The question is how to generate these in a script.  Write-Error generates a non-terminating error, but throw generates a terminating error.  Of course, one can specify the -erroraction to override.


More on errors: 
There are often several non-terminating errors if one runs get-childitem c:\windows, because wherever one does not have access, an error appears, but the "getting" continues.  The terminating error brings execution to a halt, so  get-childitem c:\windows -erroraction stop would end all further code execution  at the first "access denied" (unless Trap or Try/Catch is used).

Friday, July 20, 2012

Select Object Expand Property

Many times over the last year, I have wanted to get a specific property of an object, but I didn't have the exact object, just results from a cmdlet like Get-Acl.  So I would wrap the rest of the code in parentheses and write . and the property name.  Not my favorite thing to do.  Example:
(Get-Acl p:).access


I recently learned about Select-Object having an -ExpandProperty.  This means that instead of the parentheses, I can pipe to Select-Object -ExpandProperty, like so:
Get-Acl p: | select-object -expandproperty access


While it takes more words, I like it because it preserves the forward flow and means I don't have to go back and add parentheses.  This is a simple case, but I hope it illustrates my point.

Thursday, July 19, 2012

Accessing the Registry by String

PowerShell has its own shortcut syntax for accessing the registry HKEYs, like hkcu:.  It is part of the system of providers and psDrives.  But other programs deal with the whole HKEY name, like hkey_current_user.


I just learned that in a registry path string "hkcu:\" is equivalent to "registry::hkey_current_user\".  So if you have a long path for a registry, just tack on that "registry::", and you'll be fine.  This is also necessary for any registry HKEYs that don't have abbreviations, as HKCU and HKLM do (unless you make your own PSDrive).  

Variables in PowerShell Strings

One of the amazing things about PowerShell compared to other scripting languages (VBScript) is its ability to resolve variables within strings.  (http://technet.microsoft.com/en-us/library/ee692790.aspxCalled variable expansion, this new feature looks to make concatenation a thing of the past.  Furthermore, with PowerShell's handling of double versus single quotes, there will no longer be cases of writing four quotes just to get one (escaping a quote with a quote).


However, one thing tripped me up for the last year, until I learned the answer from training with Ashley McGlone. (http://blogs.technet.com/b/ashleymcglone/about.aspx)  The variable expansion didn't help when accessing a property or method of an object, or item in an array.  So I would have a lot of "$a" + $b.toString() + "c" for example.  Then, he showed me the secret:


In a string, I can use $(), and put whatever code I want into it, even an if statement or code block!  So the previous example becomes "$a$($b.toString())c", which is much more compact.  Furthermore, PS3 ISE will color the items in parentheses as variables and members instead of red like strings, making it easier to see what is going on inside them.


One more reason I really enjoy PowerShell!

Auto-Indenting in PowerShell ISE

Just learned a very amazing thing about the PowerShell ISE (including from PS2)!  If one selects multiple lines, then pressing tab will indent them all.  Pressing shift+tab will un-indent them all.  
This is so wonderful! I don't know how many times over the last year I added an if or try block, and I either manually spaced all the lines or just left it alone.  Now, it takes two seconds, and my code looks amazing!