Microsoft Ignite 2022 Review | A good start, but hopefully not repeated

Before we talk about Ignite 2022, let’s layout my personal biases first. I have attended 16 Microsoft TechEd/Ignite Conferences, not having missed one starting in 2004. A fact I hold with a lot of pride. I largely attribute my professional achievements and skills to these events. While it’s always fun to watch newbies miss the important parts of the event for parties, free beer, expo swag, etc., the real professionals use these events to stay ahead of the constant state of change in the industry. One more bias, I do have a distaste for Microsoft Marketing in recent years. They overachieve to sell products that don’t exist yet, or ones that do, but don’t function at the levels they claim. More why that matters later. 

I am a Microsoft Administrator of 20 years who witnessed the fall of Novell, deployed NT4 and then AD2000+. Fast-forward to now:  ahead of the curve with thousands of Azure AD joined (not hybrid) workstation clients, over 200 SAML SSO apps via Azure AD, MFA with Number Matching (aka Password-less) as part of the onboarding process, and almost no VPN requirements for my end users. 

Sadly, I must admit I didn’t get enough out of the event. What upsets me most about this fact, is that next year is slated to be another two-day format which is core to why I didn’t get enough out of the event this year. 

“Two days isn’t enough.”

Both mornings had keynotes that consumed over 25% of the entire event. Those keynotes can help provide guidance on where Microsoft thinks it is going. However, it’s mostly just Microsoft Marketing laying groundwork for hopefully self-fulfilling prophecies. Increasingly, they are selling ideas or concepts of future features which aren’t even in private preview yet. The note on company direction is useful, but not at the cost of 25% of the event.

Mr. Nadella (Microsoft CEO) couldn’t even be bothered to deliver the keynote in person, even though the event was in his home state. This felt like a major smack in the face to those like me that flew 6 hours to attend the event in person and speaks to just how little importance that half day+ worth of keynote actually mattered. 

Let’s cover the three most critical components of Ignite. The deep technical sessions, the face time with product managers/engineers, and the networking with peers. 

Not enough Deep Technical Sessions

There is always an internal fight between event planners and speakers over the technical depth and time length of a session. Level 200 sessions are almost always useless to me. It’s a sales pitch, I don’t need the sales pitch, I’m already sold, I need to know how to deploy and manage the solution. 

Far too often the needed real-world knowledge doesn’t make it into docs.microsoft.com (which is perpetually outdated, incorrect, or simply missing critical details as a byproduct of Microsoft’s newfound agility). The level 300/400 sessions are hosted by PMs, Engineers, and MVPs. These professionals always deliver value without the filter of marketing’s specter, and they provide enough tactical information to actually start deploying solutions (or avoiding the gotchas). 

There were not enough deep technical sessions. This gets back to my point that a day and a half isn’t enough time to cover all Microsoft product solutions that I need to be an expert in. There wasn’t even a specific session about Microsoft Teams Shared Channels, and that’s the exact kind of session I needed and expected this year.

Face time with Product teams” 

The next most important feature of these events is face time with product managers and engineers. It’s where I can really get straight answers. Its access so pure and helpful to solving our design issues or providing critical feedback that tends to have an impact on future releases. 

I had almost no face time this year, which was infuriating. There wasn’t enough expo space for each product team to have their own area. Instead, a scheme was devised to use that precious day and a half of session time to have “ask the expert” time windows where a given product team might be in a specific area for about 2 hours. If those two hours overlapped with a not-to-be-missed-session you ended up having to choose. 

Opportunities to Network with Peers wasn’t as prevalent as it should have been” 

There were very short periods of time between sessions. This left little time to strike up conversations with people I was sitting next to. Also, the meals were so basic that people didn’t spend a lot of time at meals nor was there even an hour to eat lunch. The lack of a proper vendor expo hall made this worse as there was no reason to stick around for the end of day free drinks and snacks. 

Cost and Time Constrained”

I gave the Microsoft events team a lot of leeway for this event. I wouldn’t be shocked if they didn’t know if they were going to put on the event at the start of 2022. This short window of time to throw the event together caused things like no swag. To be clear, I don’t care if I get a 17th backpack and in fact my wife will be thrilled to not have to make me pick one to toss this year. But a lot of people were wondering if Microsoft was being cheap with no swag. I don’t think so for that, I think logistically they couldn’t pull it off. 

But on the topic of cheap, I wonder how much the event budget played a part. Less attendees, far less vendors, perhaps many of the issues like length of event, lack of enough large session rooms, not enough space in the hub for all product teams to have a home base, or even lack of enough proper sessions – can these all be blamed on cost? 

“This was a v1 Hybrid Infant event”

Microsoft Event staff seem downright giddy about flushing out this half in person half online format. I had heard comments about perhaps future years would have multiple in person locations and sessions broadcasted to other locations and to remote users. 

While I think the idea is “cool”, I think the event staff are losing sight of what the event “should” be. I get this awful feeling the event is turning into one big sales pitch instead of what it “needs” to be for education. More now than ever before, the lack of authored books or proper documentation coming from the product teams means this event must fill the gap. That is, if Microsoft wants to see its customer deploy its new solutions. 

One misconception that was abundantly clear, was the idea that we would waste part of our day and a half of session time watching the online only content. While it’s true, many of the sessions were recorded or were online only, I think that skips an important fact:  after this week my carriage turns back into a pumpkin. I will be thrusted into never ending backlogs and my time for skills advancement will be over.

Speaking of the rushed chaotic nature of the event,  I was not the only person who thought there were sessions on Friday the 14th. With this misunderstanding, I booked my travel home on Saturday (#NoRedEyes). That left me in Seattle for the whole day with no event to go to. I ended up finding great spots in Starbucks Roastery and the Seattle public Library to get through as many recorded sessions as I could. At 1.25x play speeds, and armed with skip 10 seconds ahead, I did in fact get through 10 of them. That is far more than the other days. If we can’t persuade Microsoft to bring back the 4.5-day format, I would likely book through Saturday again next year, just so I have that one last day to learn more. 

For 2023 I would like to see the event restored to almost 5 days. This would ensure enough time to jam in all those level 200 keynotes / sales pitches and leave room for the level 300 sessions for my colleague’s needs. They also need a big enough hub so that each product team has a defined space, and they need to force those experts to be in that space during end of day drinks and food. They need larger session rooms, and more of them. They need to encourage more MVPs to submit technical session ideas. Better yet, they should open up to customers asking what sessions they would like to see (Microsoft Teams Shared Calendars cough cough). They need to make the gap in between sessions larger, at least 30 minutes, and a full hour for lunch so there is time to go into the hub. They need to run sessions until 6pm and start them earlier (like they did in past years).  

To be clear, I learned things, just not enough for a whole year. Almost like Moore’s law, the rate of change in M365/Azure is accelerating year after year, and I’m getting more staff to manage it all. I need more technical information to be as successful as in previous years. Like I said, this year was the first one back. Microsoft gets a pass this year, but next year can’t be like this year, or I fear I won’t be able to keep at the bleeding edge of innovation and security at my company. 

PowerShell Error: The underlying connection was closed: An unexpected error occurred on a send

I got mad the other day, trying to do a simple wget (i.e. invoke-webrequest) to an Azure Function I made and I was getting:

The underlying connection was closed: An unexpected error occurred on a send

I tried switching to .NET Webclient but still same error.

What was more frustrating is that it worked on my dev machine, worked on the server I was running to code on in a browser, just not in powershell.

The Fix

Apparently PowerShell version 5 defaults to TLS 1.0. Azure Functions require TLS 1.2. The fix is super simple, just add this in your code on its own line:

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

Secure PowerShell Scripts running via Windows Task Scheduler using MD5 Hashes to safeguard against Tampering

Over the years the number of Task Scheduled based PowerShell scripts has increased. However, this poses serious potential security risks.

The Security Issue

Given that these tasks commonly run as a service account, with additional rights, it is a potential attack vector.

Simply changing the underlying script can allow a hacker access to anything the service account has access to.Even signing the scripts can be useless as the system can be configured to ignore signing.

The Solution

I have created this one-liner that Task Scheduler can use that will only run the script if the hash of the script matches the hash listed in the one-liner. If someone tries to change this in Task Scheduler they would be required to reenter the proper password.

powershell.exe -command if ([System.BitConverter]::ToString((New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider).ComputeHash([System.IO.File]::ReadAllBytes(‘C:\temp\test.ps1‘))) -eq ‘33-CD-2A-54-ED-F3-0F-94-5F-D2-97-D9-FE-4F-45-79‘) {. c:\temp\test.ps1} else {Send-MailMessage -SmtpServer smtp.server.domain.com -From whatever@domain.com -To you@domain.com -Subject ‘Failed to Run Script – Hash Not Correct’}

Notes about One Line Script Executor

  • You need to replace c:\temp\test.ps1 with the path to your script. (two places in this example)
  • You must supply the hash of the script. (use the following command to get it)

[System.BitConverter]::ToString((New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider).ComputeHash([System.IO.File]::ReadAllBytes(‘C:\temp\test.ps1‘)))

  • Script will email you if hash fails.
    • Change TO: and FROM: to match your needs.
  • Do NOT use double quotes in this script, do NOT forget that CMD will pass this to PowerShell, and will strip out double quotes.

If this helped you or perhaps you have suggestions to make it better, please do leave them in the comments.

Enjoy

-Eric

Search Active Directory for Specific Word or Phrase (string) in a Group

Ever tried to search for a group by name but the part you know is in the middle? Did you think you would be smart and go to the advanced tab then do “blah” contains, hit search and find nothing?

Quickest way to find is actually via PowerShell

Get-ADGroup -Filter {Name -like “*blah*”} | select SAMAccountName

Works great!

Enjoy

-Eric

Powershell | Using Modify AD Groups with Alternate Credentials

Quick one. Had an issue where I needed to remove a user from a AD group in another domain. To my surprise it was harder then I had thought. At first I settled on using set-QADGroupMember (the Quest Powershell CMDLET) as it takes -connectionusername and -connectionpassword. However it was dog slow. I think that was due to being over a WAN link and it was querying all members (which took about 2-3 mins).

I needed something swifter. I went directly to the .NET controls and reduced the time to about 15 second.

$GroupDN = “LDAP://CN=GroupName,OU=Distribution Lists,DC=domain,DC=local”
$Group = New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList $GroupDN,”username”,”Password”
#To Add
$Group.Properties[“member”].Add(“DN of the User you wish to add”)
#To Remove
$Group.Properties[“member”].Remove(“DN of the User you wish removed”)
$Group.CommitChanges()
$Group.Close()

Enjoy!

-Eric

Powershell | Get Current User Principle Name (UPN)

Quicky,

I had a need to write a Powershell script that would figure out what the current users UPN (User Principle Name) was. Believe it or not I was dumbfounded there wasn’t a good post on it anywhere.  So here is the code:

 

$strFilter = “(&(objectCategory=User)(SAMAccountName=$Env:USERNAME))”
$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.PageSize = 1
$objSearcher.Filter = $strFilter
$objSearcher.SearchScope = “Subtree”
$objSearcher.PropertiesToLoad.Add(“userprincipalname”) | Out-Null
$colResults = $objSearcher.FindAll()

$UPN = $colResults[0].Properties.userprincipalname
$UPN

 

Enjoy, if you needed this and found it here please let me a comment, always glad to hear when these things help people out!

Powershell | The Last $Error and Emailing it

OMG some things in Powershell are just too confusing to be useful. What if you need to see the last error message again. What if you want to write it into your script to email you when the error happens?

Well first, its all in $Error

However, $Error is an array.  To access it really requires notation like this:

$Error[0]

The [0] says give me back the last error. Where [1] would say to give me back the second to last error message.

The Problem….

Ok now here is where it gets “funky”. If you just type $Error[0] you get the entire error message like so: (note I am using an error message from some Lync work I have been doing, the names have been changed to protect well me lol)

Set-CsUser : Management object not found for identity “Jerry.Springer@Contoso.com”.
At C:\Scripts\EnableLyncUsers.ps1:138 char:15
+                 Set-CsUser <<<<  -Identity $user.UserPrincipalName -SipAddress $user.UserPrincipalName
    + CategoryInfo          : NotSpecified: (:) [Set-CsUser], ManagementException
    + FullyQualifiedErrorId : Microsoft.Rtc.Management.AD.ManagementException,Microsoft.Rtc.Management.AD.Cmdlets.S
   etOcsUserCmdlet

BUT…. if you type write-host $Error[0] you get this:

Management object not found for identity “Jerry.Springer@Contoso.com”.

So what gives right??? Why when you use Write-Host OR even better when you try to email $Error[0] do we get the crappy short error message? Well I don’t have the answer BUT I do have a great work around.

The Solution….

[string]$ErrorString = $Error[0].Exception
[string]$ErrorString = $ErrorString + ” `n `n ”
[string]$ErrorString = $ErrorString + $Error[0].InvocationInfo.PositionMessage

(that’s 3 lines BTW)

As far as I can tell the only thing one needs are the short error message and the line, script, and command. To do this use the code above and then simply use Write-Host or email that new $ErrorString variable. If you need other data follow the info below from how I figured this out.

Emailing the Error? Simply use this code (replace stuff inside of < > then remove the < >):

    [string]$ErrorString = $Error[0].Exception
    [string]$ErrorString = $ErrorString + ” `n `n ”
    [string]$ErrorString = $ErrorString + $Error[0].InvocationInfo.PositionMessage

    $SmtpClient = new-object system.net.mail.smtpClient
    $MailMessage = New-Object system.net.mail.mailmessage
    $SmtpClient.Host = “<SMTP IP OR NAME>”
    $mailmessage.from = <from@domain.com>
    $mailmessage.To.add(“email1@domain.com,email2@domain.com”)
    $mailmessage.Subject = “<Subject of Email>”
    $MailMessage.IsBodyHtml = $false
    $mailmessage.Body = $ErrorString
 
    $smtpclient.Send($mailmessage)

How did I figure this out?

First I indexed $Error to get me the first result [0]

Next I used the power of Get-Member

$Error[0] | Get-Member

This dumped out all the properties

TypeName: System.Management.Automation.ErrorRecord

Name                  MemberType     Definition                                                                    
—-                  ———-     ———-                                                                    
Equals                Method         bool Equals(System.Object obj)                                                
GetHashCode           Method         int GetHashCode()                                                             
GetObjectData         Method         System.Void GetObjectData(System.Runtime.Serialization.SerializationInfo inf…
GetType               Method         type GetType()                                                                
ToString              Method         string ToString()                                                             
CategoryInfo          Property       System.Management.Automation.ErrorCategoryInfo CategoryInfo {get;}            
ErrorDetails          Property       System.Management.Automation.ErrorDetails ErrorDetails {get;set;}             
Exception             Property       System.Exception Exception {get;}                                             
FullyQualifiedErrorId Property       System.String FullyQualifiedErrorId {get;}                                    
InvocationInfo        Property       System.Management.Automation.InvocationInfo InvocationInfo {get;}             
PipelineIterationInfo Property       System.Collections.ObjectModel.ReadOnlyCollection`1[[System.Int32, mscorlib,…
TargetObject          Property       System.Object TargetObject {get;}                                             
PSMessageDetails      ScriptProperty System.Object PSMessageDetails {get=& { Set-StrictMode -Version 1; $this.Exc…

All of the properties normally can be accessed like this:

$Error[0].Exception

But if you try to write-host $Error[0].InvocationInfo you get:

System.Management.Automation.InvocationInfo

Well that’s not very useful… the reason for this is there are deeper items in the $Error[0].InvocationInfo tree. So if we go ahead and whip out get-member again on $Error[0].InvocationInfo lets see what we get:

TypeName: System.Management.Automation.InvocationInfo

Name             MemberType Definition                                                                             
—-             ———- ———-                                                                             
Equals           Method     bool Equals(System.Object obj)                                                         
GetHashCode      Method     int GetHashCode()                                                                      
GetType          Method     type GetType()                                                                         
ToString         Method     string ToString()                                                                      
BoundParameters  Property   System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=2.0.0.0, Cu…
CommandOrigin    Property   System.Management.Automation.CommandOrigin CommandOrigin {get;}                        
ExpectingInput   Property   System.Boolean ExpectingInput {get;}                                                   
HistoryId        Property   System.Int64 HistoryId {get;}                                                          
InvocationName   Property   System.String InvocationName {get;}                                                    
Line             Property   System.String Line {get;}                                                              
MyCommand        Property   System.Management.Automation.CommandInfo MyCommand {get;}                              
OffsetInLine     Property   System.Int32 OffsetInLine {get;}                                                       
PipelineLength   Property   System.Int32 PipelineLength {get;}                                                     
PipelinePosition Property   System.Int32 PipelinePosition {get;}                                                   
PositionMessage  Property   System.String PositionMessage {get;}                                                   
ScriptLineNumber Property   System.Int32 ScriptLineNumber {get;}                                                   
ScriptName       Property   System.String ScriptName {get;}                                                        
UnboundArguments Property   System.Collections.Generic.List`1[[System.Object, mscorlib, Version=2.0.0.0, Culture=…

Ah… there’s more stuff. Lastly I just needed to figure out what items inside of $Error[0].InvocationInfo I needed. Turns out just one thing. So to write-host it all I needed to do is call:

Write-Host $Error[0].InvocationInfo.PositionMessage

Hope that opens your mind to how more complex objects work in Powershell.

Hey!

Did I help? Make Sense? Something Wrong? Put it in the comments. Love to hear when my write-ups help folks out.

Enjoy

-Eric

Get all SMTP Address from Public Folders or Groups or anything in Exchange!

Here is a quick one, just change your mail domain where it says “MyDomain.com”

Public Folders Only:

Get-recipient -RecipientTypeDetails PublicFolder | select Name -ExpandProperty EmailAddresses | ? {$_.SMTPAddress -like “*MyDomain.com*”} | select Name, SMTPAddress

To pump to CSV:

Add this to the end: | Export-CSV C:\Filename.csv

Other  RecipientTypeDetails types? Just change the RecipientTypeDetails to one or more of the following (comma delimited):

  • MailUniversalDistributionGroup
  • MailUniversalSecurityGroup
  • DynamicDistributionGroup
  • MailNonUniversalGroup
  • MailUser
  • UserMailbox
  • PublicFolder
  • MailContact
  • DiscoveryMailbox
  • SharedMailbox
  • RoomMailbox

Set Processor Affinity with Powershell

Hey all I know its been far to long since my last post. I have been doing a lot of great things with powershell and I am going to start sharing them as they come up. Here was a helpful one this morning…

So the Backup Server is going nuts with these storageservice.exe processes consuming 100% of the CPU. It makes it very hard to troubleshoot when the server doesn’t have enough CPU to let the OS run.

image

Below is a script I just created that takes all of them and sets them to only use cores 1 – 4 (basically only allowing it 50% of the total CPU power.


# Set Processor Affinity by adding the number together. For cores 1 – 4 its 15 for example.
# 1 (CPU 1)
# 2 (CPU 2)
# 4 (CPU 3)
# 8 (CPU 4)
# 16 (CPU 5)
# 32 (CPU 6)
# 64 (CPU 7)
#128 (CPU 8 )

$instances = Get-Process storageservice
foreach ($i in $instances) {
    $i.ProcessorAffinity=15
}


Ah much better, now time to figure out why its going nuts….

image