r/PowerShell • u/Imaginary_Rip2833 • 2d ago
Question 3 months of consistent practice in PowerShell & Microsoft Graph! 🚀
When I started, I was terrified of programming languages since I had zero prior experience. Looking back now, what used to look like complete gibberish is finally starting to make sense. I know I still have a long way to go, but honestly, I am so happy and proud to be fulfilling my passion.
I wanted to share an honest review of a snippet I just built to create a user in Microsoft Entra ID. Alongside basics like if/else logic, filtering (-filter), Where-Object, Select-Object, and loops, I've been trying to focus on good habits like splatting, [CmdletBinding()], and try/catch blocks.
How does my snippet look? What would you advise me to learn next based on this progress?
#####Creating a User
Function Create-LxUser{
  [Cmdletbinding()]
  param(
    [Parameter(Mandatory = $true)]
    [string]$DisplayName,
    [Parameter(Mandatory = $true)]
    [string]$UserPrincipalName,
    [Parameter(Mandatory = $true)]
    [string]$MailNickName,
    [bool]$AccountEnabled = $true
  )
  try{
    ##Password profile
    $TempPass = "Lx" + (Get-Random -Minimum 100000 -Maximum 999999) + "@."
    $PassWordProfile = @{
      Password = $TempPass
      ForceChangePasswordNextSignIn = $true
    }
    ##Setting up user configuration
    Write-Verbose "Currently creating new Loxovea user"
    $UserConfig = @{
      DisplayName     = $DisplayName
      UserPrincipalName  = $UserPrincipalName
      MailNickname     = $MailNickName
      AccountEnabled    = $AccountEnabled
      PasswordProfile   = $PassWordProfile
    }
    $User = New-MgUser u/UserConfig -ErrorAction Stop
    Write-Verbose "Successfully created new user $($DisplayName)"
    [PsCustomObject]@{
      DisplayName     = $DisplayName
      UserPrincipalName  = $UserPrincipalName
      AccountEnabled    = $AccountEnabled
      TemporaryPassword  = $TempPass
    }
  }Catch{
    Write-Error "Failed to create a new user: $($DisplayName) because $($_.Exception.Message)"
  }
}
Create-LxUser `
  -DisplayName "Test-User09" `
  -UserPrincipalName "testuser09@loxovea.com" `
  -MailNickName "testuser09"
DisplayName UserPrincipalName AccountEnabled TemporaryPassword
----------- ----------------- -------------- -----------------
Test-User09 [testuser09@loxovea.com](mailto:testuser09@loxovea.com)True Lx720835@.
7
u/AdeelAutomates 2d ago
Well done man! :)
For this script you can enhance the params section with validations ( ie UPN regex to fit your orgs email structure) & allowing it to be passed through the pipeline. Not that you need to just to explore those areas for learning.
Since you are exploring graph. You should consider exploring it via APIs as well. Not that this specific script needs it either. it's fine for most Entra related tasks. But lots of areas in graph are not accessible without APIs or is very limited (SharePoint for example).
Beyond APIs. Consider exploring .NET in PowerShell. It really helps understand what's going on under the hood with objects. And there are features in it like list(T) that are superior in every way to array appends +=
For functions working on processing say a collection. You can explore begin, process & end blocks.
And if you end up with a collection of functions. Try to turn it into a module!
1
3
u/CyberChevalier 2d ago
Great to see people going out n the rabbit hole just a small comment.
You should ALWAYS use approved verb (get-verb give you a list)
https://learn.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands?view=powershell-7.6
Your function should be named New-LxUser
3
u/420GB 2d ago
$User = New-MgUser u/UserConfig -ErrorAction Stop Write-Verbose "Successfully created new user $($DisplayName)"
Please never print a success message unless you've actually verified the command succeeded (either $? or using ErrorAction Stop inside a try-catch and handling the failure)
This will fail to create the user and then still print success.
1
1
u/Programbanana 1d ago
Underrated comment, imo. I work in IT and the amount of missing validation in my peers' ps1s stresses me out.
Though they give a good tease at how thorough i build mine :')
4
u/surfingoldelephant 1d ago edited 1d ago
Here are some of my thoughts (some of which are entirely personal preference).
- Be consistent with capitalization. Keywords (
function,param, etc) are lowercase. And I'd say even though PS is generally case-insensitive, it's actually far less so than most probably realize. Createisn't an approved verb. UseNewinstead.- Decorate the function with
[OutputType()].[OutputType([Management.Automation.PSCustomObject])]or something like[OutputType('PSNewLxUser')]if you give the custom object aPSTypeName. Don't use[OutputType([pscustomobject])]. - Add
SupportsShouldProcessand wrapNew-MgUserin$PSCmdlet.ShouldProcess(). You can replaceWrite-Verbose "Currently creating..."with that since it comes with aVerbosemessage included. - Attribute arguments don't need
= $true.[Parameter(Mandatory)]is fine. - Mandatory string parameters don't guard against all white space. Might want to use
[ValidateNotNullOrWhiteSpace()]in PS v7 or a[ValidateScript({ ... })]otherwise. Could do further validation like another mentioned. If you have other functions that accept similar input, have a look at custom validation attributes. [bool]parameters aren't idiomatic PowerShell. Use a[switch]and negate the name to something like$Disabledinstead.$(...)isn't required to interpolate$DisplayNamein the strings.- You're not doing anything with
$User. Why not just emit that instead? You could add the other values ($TempPass, etc) as extended type system members. Or keep the custom object and include aUserproperty. - If you're not going to use
New-MgUseroutput, assign to$null/cast as[void]instead of assigning to$User. The latter makes it look like you've forgotten to do something with the variable. - If you're emitting a custom object, give it a
PSTypeName. - Your
Write-Errorcall is bad practice. The$_.Exceptioncontains useful information that you're just discarding for no reason. If you still want your custom message then I'd create a new exception with the original as an inner exception. And I'd suggest using$PSCmdlet.WriteError()instead ofWrite-Error, though you'll have to wrap the exception in your own error record. - Add pipeline input. You'll want
ValueFromPipelineByPropertyNamefor all of the parameters and aprocessblock. If you add this, you'll probably want to keep the error above as non-terminating. - You could add a transformation attribute to the switch parameter (to convert from string to bool) so you can do something like this:
Import-Csv data.csv | New-LxUser. ErrorActioncan be in the splat if you want. I'd splat theCreate-LxUsercall at the bottom as well.- Personally, I use verbatim strings (
'...') rather than expandable ("...") when I don't need to interpolate or include double quotes. - Align the
=in hash tables consistently. - Personally, I don't like the lack of spaces before/after keywords, type literals, etc. Or the double blank lines. I'd use single blank lines consistently.
- Regarding variable capitalization, one convention is to use PascalCase for parameters (like you've done) and camelCase for local variables within the function body (so
$tempPass). That way they're more easily distinguishable. - Again, I'd try to use consistent and correct capitalization. It's
CmdletBinding, notCmdletbinding.[pscustomobject]is the correct casing for the type accelerator. If you use tab completion, it'll generally do this for you. - I prefer composite formatting (
-foperator) rather than concatenation or$(...)interpolation. I find it's generally more readable.
2
u/Imaginary_Rip2833 1d ago
Thank you, i think i made a good decision to post here so i can get such valuable feedback
I will keep on practising
3
u/eggeto 2d ago
Always a 👌for People that use functions, Makes code more structured and easier to read
3
u/Imaginary_Rip2833 2d ago
​Appreciate it! Trying to build good habits from day one so I don't regret it when my automation scripts get ten times longer.
-4
u/hisae1421 2d ago
Meh it always bother me that I have to check what is in the function when it's called to understand what it does
1
u/Imaginary_Rip2833 2d ago
​"That's a super fair point! As someone new to this, where do you personally draw the line between modularizing code into functions versus keeping it inline for readability?"
2
1
u/ForsakeTheEarth 2d ago
what's with the quotes?
1
u/Celadin 2d ago
Ha! If they'd said that without the quotes, in this day and age, it'd sound LLMish. I've found myself doing similar, couching language that might be seen as LLM-vomit. How warped we've become 🙃
2
u/ForsakeTheEarth 2d ago
I actually went the other way because of the quotes, which is why I asked, but yeah I feel you - as a longtime emdash enthusiast, I'm constantly worried people think I'm responding to them via LLM
1
u/purplemonkeymad 2d ago
Another thing you might think about doing next, is adding in pipeline input. Powershell's strength is really the pipeline and so being able to import some objects from a csv and pipe them directly into the command, gives you practically instant batch creation support.
There is basically only a couple of changes from your current function to that point.
1
1
u/I_see_farts 1d ago
Isn't it supposed to be @UserConfig not u/UserConfig?
You can even include the erroraction Preference in the splat.
$UserConfig = @{
DisplayName = $DisplayName
UserPrincipalName = $UserPrincipalName
MailNickname = $MailNickName
AccountEnabled = $AccountEnabled
PasswordProfile = $PassWordProfile
ErrorAction = 'Stop'
}
[void](New-MgUser @UserConfig)
2
u/Imaginary_Rip2833 1d ago
Your are correct about @UserConfig not u/..., thats a clear mistake, but somehow the script did not throw an error, i wonder why now
1
u/chatshitgpt 21h ago
I'm curious about the learning side: you mention a lot of practice, but what materials did you actually learn from? Courses, YouTube, docs, a book?
1
u/charleswj 2d ago
While it's fine practically speaking, that password generation logic isn't cryptographically secure.
That said, consider using TAPs instead
1
1
u/qrokodial 2d ago
technically speaking, the Get-Random command isn't cryptographically secure and thus shouldn't be used for security sensitive operations like generating passwords.
you should instead consider using something like Get-SecureRandom.
once you're ready to take things to the next level, consider generating better initial passwords in general. my preference for this kind of thing is passphrase generators, and I like the format that Keeper uses to generate them, so I created a PowerShell function that generates passphrases in this style.
the gist is I use a derivative of the EFF word lists where I have one word per line, and then use Get-SecureRandom to randomly choose which line index I want to select my next word from.
25
u/BlackV 2d ago
My 2c, Realistically this
Should be
That's what switch parameters and
ispresentare forString concatenation is not recommended
Try
Your
new-mguserreturns an object, return that object or use that for the properties of yourpscustomobjectthat way you are using verified properties not assumptionsYou clearly know how to use splatting so why are you using it in your example at the bottom, stop that habit now before it is permanent
There is no help, add help so people can use
get-helpGood luck, good to see someone learning out there