Generate random password - powershell

I have this script in PowerShell. I want set up random password for each user import from file and I want all comments in popup cmd I want save in other txt file. How can I make this?
#
# Description: Enable accounts, reset passwords and set change password option at first logon.
#
Import-Module ActiveDirectory
# Set default password "XXX"
$password = ConvertTo-SecureString -AsPlainText “XXX” -Force
# get account list from UserList.txt
# 1 user per line
$users = Get-Content -Path 'G:\Shares\XXX\ResetPassword\UserList.txt'
ForEach ($user in $users)
{
# Set default password for account
Get-ADUser $user | Set-ADAccountPassword -NewPassword $password -Reset
# Set option to change password at first logon
Get-ADUser $user | Set-AdUser -ChangePasswordAtLogon $true
# Enable account
Enable-ADAccount -Identity $user
Write-Host “Password change for: $user”
}
Write-Host “New password for all users: XXX”
# ————- End ———–
Read-Host -Prompt "Click enter to quit"

You can use the static GeneratePassword .net method to generate a password:
Add-Type -AssemblyName System.Web
[System.Web.Security.Membership]::GeneratePassword(10, 3)
Edit:
You have to change your script to:
#
# Description: Enable accounts, reset passwords and set change password option at first logon.
#
Import-Module ActiveDirectory
Add-Type -AssemblyName System.Web
$unsecuredPwd = [System.Web.Security.Membership]::GeneratePassword(10, 3)
# Set default password "XXX"
$password = ConvertTo-SecureString -AsPlainText $unsecuredPwd -Force
# get account list from UserList.txt
# 1 user per line
$users = Get-Content -Path 'G:\Shares\XXX\ResetPassword\UserList.txt'
ForEach ($user in $users)
{
# Set default password for account
Get-ADUser $user | Set-ADAccountPassword -NewPassword $password -Reset
# Set option to change password at first logon
Get-ADUser $user | Set-AdUser -ChangePasswordAtLogon $true
# Enable account
Enable-ADAccount -Identity $user
Write-Host "Password change for: $user"
}
Write-Host "New password for all users: $unsecuredPwd"
# ————- End ———–
Read-Host -Prompt "Click enter to quit"

#MartinBrandl's answer works but requires importing System.Web. Below is a bespoke solution that'll generate a random printable ASCII string using the system's cryptographic generator and a little bit of maths; sample usage: [SecureRandom]::GeneratePassword(64). Integrating into a script is left as an exercise for the reader...
class SecureRandom {
hidden static [System.Security.Cryptography.RandomNumberGenerator] $m_randomNumberGenerator = ([System.Security.Cryptography.RNGCryptoServiceProvider]::new());
hidden static [System.UInt32] NextUInt32([System.UInt32]$exclusiveHigh) {
[System.UInt32]$range = ([System.UInt32]::MaxValue - ((([System.UInt32]::MaxValue % $exclusiveHigh) + 1) % $exclusiveHigh));
[System.UInt32]$result = 0;
do { # perform rejection sampling to avoid bias; see https://en.wikipedia.org/wiki/Rejection_sampling, https://en.wikipedia.org/wiki/Rejection_sampling, https://www.pcg-random.org/posts/bounded-rands.html
$result = [SecureRandom]::NextUInt32();
} while ($result -gt $range);
return ($result % $exclusiveHigh);
}
static [System.String] GeneratePassword([System.UInt32]$length) {
$result = [char[]]::new($length);
for ($i = 0; ($i -lt $length); ++$i) { # generate a random ASCII character within the "printable" decimal range of 32-126; this currently "wastes" 3 bytes per iteration and could be made more efficient...
$result[$i] = ([char][SecureRandom]::NextUInt32(32, 126));
}
return [System.String]::new($result);
}
static [byte[]] GetBytes([int]$count) {
[byte[]]$result = [byte[]]::new($count);
[SecureRandom]::m_randomNumberGenerator.GetBytes($result);
return $result;
}
static [System.UInt32] NextUInt32() {
return [System.BitConverter]::ToUInt32([SecureRandom]::GetBytes(4), 0);
}
static [System.UInt32] NextUInt32([System.UInt32]$x, [System.UInt32]$y) {
if ($x -gt $y) {
$z = $x;
$x = $y;
$y = $z;
}
[System.UInt32]$range = ($y - $x);
if ($range -ne [System.UInt32]::MaxValue) {
return ([SecureRandom]::NextUInt32($range + 1) + $x);
}
else {
return [SecureRandom]::NextUInt32();
}
}
}

Related

PowerShell "New-ADUSer" password complexity error

I will create 100 random AD users via script. But , I am getting the following error message for some random users.
New-ADUser : The password does not meet the length, complexity, or history requirement of the domain.
Get-ADDefaultDomainPasswordPolicy
output :
ComplexityEnabled : True
DistinguishedName : DC=contoso,DC=local
LockoutDuration : 00:00:00
LockoutObservationWindow : 69.10:39:00
LockoutThreshold : 5
MaxPasswordAge : 90.00:00:00
MinPasswordAge : 3.00:00:00
MinPasswordLength : 8
objectClass : {domainDNS}
objectGuid : 346664da-c908-470e-9fc3-5487983c92ae
PasswordHistoryCount : 12
ReversibleEncryptionEnabled : False
Here is my script :
$UserList = Import-CSV -Path C:\temp\CreateUsers.csv
$targetOU = 'OU=Test,DC=contoso,DC=local'
$upnDomain = 'contoso.local'
$Path = "C:\temp\output.csv"
foreach ($Person in $UserList) {
#If username does not exist
if(-not(Get-ADUser -Filter "SamAccountName -eq '$($Person.Sam)'")) {
$PassWord = -join ([char[]]"!##$%^&*0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" |Get-Random -Count 10)
$useritems = #{
GivenName = $Person.Firstname
Surname = $Person.LastName
AccountPassword = ConvertTo-SecureString -String $Password -AsPlainText -force
ChangePasswordAtLogon = $false
Enabled = $true
DisplayName = "$($Person.Firstname) $($Person.Lastname)"
Name = "$($Person.Firstname) $($Person.Lastname)"
SamAccountName = $Person.Sam
UserPrincipalName = "$($Person.Sam)#$upnDomain"
}
New-ADUser #useritems -Path $targetOU
Add-Content -Path $Path -Value "Username: $($Person.Sam) Password: $Password"
}
}
To avoid creating passwords that do not comply to the domains password complexity rules, you could use this helper function:
function Test-DomainPassword {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$Password,
[Parameter(Mandatory = $false)]
[string]$AccountSamAccountName = $null,
[Parameter(Mandatory = $false)]
[string]$AccountDisplayName = $null
)
# [Microsoft.ActiveDirectory.Management.ADEntity]
$PasswordPolicy = Get-ADDefaultDomainPasswordPolicy -ErrorAction SilentlyContinue
If ($Password.Length -lt $PasswordPolicy.MinPasswordLength) {
Write-Verbose "Password '$Password' is too short. Minimal length is $($PasswordPolicy.MinPasswordLength)"
return $false
}
if (![string]::IsNullOrWhiteSpace($AccountSamAccountName) -and $Password -match $AccountSamAccountName) {
Write-Verbose "The password '$Password' includes the users SamAccountName"
return $false
}
if (![string]::IsNullOrWhiteSpace($AccountDisplayName)) {
# if ANY PART of the display name that is split by the characters below, the password should fail the complexity rules.
$tokens = $AccountDisplayName.Split(",.-,_ #`t")
foreach ($token in $tokens) {
if (($token) -and ($Password -match "$token")) {
Write-Verbose "The password '$Password' includes (part of) the users DisplayName"
return $false
}
}
}
if ($PasswordPolicy.ComplexityEnabled) {
# check for presence of
# - Uppercase: A through Z, with diacritic marks, Greek and Cyrillic characters
if ($Password -cnotmatch "[A-Z\p{Lu}\s]") {
Write-Verbose "The password '$Password' is missing Uppercase characters"
return $false
}
# - Lowercase: a through z, sharp-s, with diacritic marks, Greek and Cyrillic characters
if ($Password -cnotmatch "[a-z\p{Ll}\s]") {
Write-Verbose "The password '$Password' is missing Lowercase characters"
return $false
}
# - Base 10 digits (0 through 9)
if ($Password -notmatch "[\d]") {
Write-Verbose "The password '$Password' is missing digits (0-9)"
return $false
}
# - Nonalphanumeric characters: ~!##$%^&*_-+=`|\(){}[]:;”‘<>,.?/
if ($Password -notmatch "[^\w]") {
Write-Verbose "The password '$Password' is missing Nonalphanumeric characters: ~!##$%^&*_-+=`|\(){}[]:;`"'<>,.?/"
return $false
}
}
return $true
}
and use that in a loop:
do {
$PassWord = -join ([char[]]"!##$%^&*0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" | Get-Random -Count 10)
} until (Test-DomainPassword -Password $PassWord)
If you add the -Verbose switch to the function call, you will see the reason why a password would fail.
Of course, this cannot test if a password meets the domains history requirement, that remains a trial and error thing..
Or you could re-use the code from Test-DomainPassword and create a helper function that returns a valid password like this:
function New-DomainPassword {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $false)]
[int]$Length = 10,
[Parameter(Mandatory = $false)]
[string]$SamAccountName = $null,
[Parameter(Mandatory = $false)]
[string]$DisplayName = $null
)
# [Microsoft.ActiveDirectory.Management.ADEntity]
$passwordPolicy = Get-ADDefaultDomainPasswordPolicy -ErrorAction SilentlyContinue
$passwordLength = [math]::Max($Length, $passwordPolicy.MinPasswordLength)
$count = 1
while ($true) {
Write-Verbose ("Generating valid password attempt: {0}" -f $count++)
$password = -join ([char[]]"!##$%^&*0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" | Get-Random -Count $passwordLength)
if (![string]::IsNullOrWhiteSpace($SamAccountName) -and $password -match $SamAccountName) {
continue # bad password, skip and try another one
}
if (![string]::IsNullOrWhiteSpace($DisplayName)) {
# if ANY PART of the display name that is split by the characters below, the password should fail the complexity rules.
$tokens = $DisplayName.Split(",.-,_ #`t")
$bad = foreach ($token in $tokens) {
if (($token) -and ($password -match $token)) { $true; break }
}
if ($bad) { continue } # bad password, skip and try another one
}
if ($passwordPolicy.ComplexityEnabled) {
# check for presence of
# - Uppercase: A through Z, with diacritic marks, Greek and Cyrillic characters
if ($password -cnotmatch "[A-Z\p{Lu}\s]") {
continue # bad password, skip and try another one
}
# - Lowercase: a through z, sharp-s, with diacritic marks, Greek and Cyrillic characters
if ($password -cnotmatch "[a-z\p{Ll}\s]") {
continue # bad password, skip and try another one
}
# - Base 10 digits (0 through 9)
if ($password -notmatch "[\d]") {
continue # bad password, skip and try another one
}
# - Nonalphanumeric characters: ~!##$%^&*_-+=`|\(){}[]:;”‘<>,.?/
if ($password -notmatch "[^\w]") {
continue # bad password, skip and try another one
}
}
# apparently all tests succeeded, so break out of the while loop
break
}
# return the new password
$password
}
and use it like this:
$UserList = Import-CSV -Path C:\temp\CreateUsers.csv
$targetOU = 'OU=Test,DC=contoso,DC=local'
$upnDomain = 'contoso.local'
$Path = "C:\temp\output.csv"
foreach ($Person in $UserList) {
#If username does not exist
if(-not(Get-ADUser -Filter "SamAccountName -eq '$($Person.Sam)'")) {
$accountName = $Person.Sam
$displayName = "$($Person.Firstname) $($Person.Lastname)"
$Password = New-DomainPassword -Length 10 -SamAccountName $accountName -DisplayName $displayName
$useritems = #{
GivenName = $Person.Firstname
Surname = $Person.LastName
AccountPassword = ConvertTo-SecureString -String $Password -AsPlainText -force
ChangePasswordAtLogon = $false
Enabled = $true
DisplayName = $displayName
Name = "$($Person.Firstname) $($Person.Lastname)"
SamAccountName = $accountName
UserPrincipalName = "$($Person.Sam)#$upnDomain"
Path = $targetOU
}
New-ADUser #useritems
Add-Content -Path $Path -Value "Username: $accountName Password: $Password"
}
}
Your random password generator is very poor and you do not guarantee that it meets your requirements. There is a .NET function, that you can use to generate random passwords: .GeneratePassword(Int32, Int32).
The first argument specifies the length of the password and the second argument specifies the minimum number of special characters.
You can use the following PowerShell function to generate a random password according to your needs:
function Get-RandomPassword {
param($Length = 8) # Default length of 8, if not provided
# Generate a random password with at least one special character
$password = [System.Web.Security.Membership]::GeneratePassword($Length, 1)
# Check password complexity
if ($password -cnotmatch '[A-Z]' -or $password -cnotmatch '[a-z]' -or $password -cnotmatch '\d') {
# Recursively generate a new password if the complexity requirements are not met
return Get-RandomPassword -Length $Length
} else {
# Return the password, if the complexity requirements are met
return $password
}
}
Example usage:
PS C:\> Get-RandomPassword -Length 10
A8$JfE1e%i
The function generates a random password of the provided length and with at least one special character. It then checks if all other (your individual) requirements are met. If not, the function will call itself recursively, until a password is found, that meets all complexity criteria.
To be honest, it's not the best design, as it might run forever in theory, but you will rather win a lottery than seeing this function running forever ;-)

Student Script for creating new users error :System.String' to the type 'System.Management.Automation.SwitchParameter

I'm pulling some user info from a .csv to create new users,
I've splatted the New User Params at the suggestion of someone here
but I'm getting this error
New-ADUser : Cannot convert 'System.String' to the type 'System.Management.Automation.SwitchParameter' required by parameter
'Confirm'.
At C:\Users\Administrator\Documents\GitHub\cyclone-internal-user-sync-1\Bamboo Attributes form a csv.ps1:68 char:28
+ New-ADUser #NewUserParms
+ ~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-ADUser], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.ActiveDirectory.Management.Commands.NewADUser
I have no idea what this is haha, I've tried adding an erroraction stop to the new-aduser but that didn't have any effect
I have added trims and a section to remove spaces from usernames. to deal with multipart names such as Van der.... etc
#Bamboo Attributes from a .csv
#Enter a path to your import CSV file
$ADUsers = Import-csv 'path'
#Bamboo Attributes from a .csv
#Enter a path to your import CSV file
$ADUsers = Import-csv 'C:\Users\Administrator\Documents\GitHub\cyclone-internal-user-sync-1\documentation\SampleUserAttributes.csv'
#$apiRequest = Get-Content -Raw -Path C:\Users\alexh\Documents\GitHub\cyclone-internal-user-sync-1\cyclone-internal-user-sync-1\fake-api-query.json | ConvertFrom-Json
foreach ($User in $ADUsers) {
$firstName = $user.FirstName.Trim()
$surname = $user.Surname.Trim()
$vaildUsernameFormat = "[^a-zA-Z_.]" # identifies anything that's _not_ a-z or underscore or .
$username = "($firstName'.'$surname)" -replace $vaildUsernameFormat, '' #removes anything that isn't a-z
$DefaultPassword = 'Pa$$w0rd'
$NewUserParms = #{
'samAccountName' = $username;
'Name' = "$firstname $surname";
'DisplayName' = "$firstname $surname";
'UserPrincipalName' = "$username#domain.com";
'GivenName' = $firstname;
'Surname' = $surname;
'EmailAddress' = $User.Email;
'AccountPassword' = (ConvertTo-SecureString $DefaultPassword -AsPlainText -Force);
'Enabled' = $true;
'Path' = "OU=Users,DC=domain,DC=com";
'co' = $User.Country;
'company' = $User.CompanyName;
'countryCode' = $user.countryCode;
'department' = $user.OrgDepartmentName;
'Employeeid' = $user.EmployeeId;
'exstentionAttribute1' = $user.ExstentionNumber;
'ipPhone' = $user.ExstentionNumber;
'L' = $user.location;
'mail' = $user.Email;
'mobile' = $user.Mobile;
'Manager' = $user.Manager;
'physicalDeliveryOffice' = $user.Branch;
'postalCode' = $user.PostalCode;
'postOfficeBox' = $user.PostOfficeBox;
'proxyAddresses' = $user.ProxyEmail;
'scriptPath' = $user.scriptPath;
'st' = $user.StreetName;
'Title' = $user.Title
}
write-host "$username this is username value"
#Check if the user account already exists in AD
if (Get-ADUser -F {
sAMAccountName -eq $username
}) {
#If user does exist, output a warning message
Write-Warning "A user account $username has already exist in Active Directory."
}
else {
#If a user does not exist then create a new user account
New-ADUser #NewUserParms
}
}
I've removed some of the user attributes just to make this a bit smaller.
here is the.csv as well in case I've messed something up there
link to .csv file on git
A little known fact about PowerShell is that you don't need to use the whole parameter name. You can use the partial name and as long as it matches only one parameter name, that's what PowerShell assumes you mean.
The one it's choking on is this:
'co' = $User.Country;
If you look at the documentation for New-ADUser, it does not have a parameter called co. So PowerShell assumes it's a partial match to a known parameter, and the closest match is -Confirm. And the value in $User.Country doesn't make any sense for the -Confirm parameter, so it throws the error.
You will have to use the -OtherAttributes parameter to set all the other attributes that New-ADUser doesn't have a dedicated parameter for:
$NewUserParms = #{
...
'OtherAttributes = # {
'co' = $User.Country;
'exstentionAttribute1' = $user.ExstentionNumber;
...
}
...
}
As commented in this and previous questions, you are using New-ADUser $NewUserParms, where it should be New-ADUser #NewUserParms.
Also, to catch errors (you did add -ErrorAction Stop), you need to put that inside a try{..} catch{..} block.
I would also change the syntax you use for the -Filter parameter. Instead of using a scriptblock syntax {something -eq someotherthing}, you should create a string like "something -eq 'someotherthing'"
Try:
# define some 'constants'
$csvFile = 'X:\Folder\NewUsers.csv' # Enter a path to your import CSV file
$invalidCharacters = '[^a-z_.]' # identifies anything that's _not_ a-z or underscore or .
$DefaultPassword = 'Pa$$w0rd'
$securePassword = ConvertTo-SecureString -String $DefaultPassword -AsPlainText -Force
# read the input csv and loop through
Import-Csv -Path $csvFile | ForEach-Object {
$firstName = $_.FirstName.Trim()
$surname = $_.Surname.Trim()
$username = ('{0}.{1}' -f $firstName, $surname) -replace $invalidCharacters
# test if a user with that name already exists
$user = Get-ADUser -Filter "SamAccountName -eq '$username'" -ErrorAction SilentlyContinue
if ($user) {
Write-Warning "A user account $username already exist in Active Directory."
}
else {
Write-Host "Creating user $username"
$NewUserParms = #{
'SamAccountName' = $username
'Name' = "$firstname $surname"
'DisplayName' = "$firstname $surname"
'UserPrincipalName' = "$username#domain.com"
'GivenName' = $firstname
'Surname' = $surname
'EmailAddress' = $_.Email
'AccountPassword' = $securePassword
'Enabled' = $true
'Path' = "OU=Users,DC=domain,DC=com"
# add other properties to set from the CSV here.
# make sure you get the parameter data types correct and always check here:
# https://learn.microsoft.com/en-us/powershell/module/addsadministration/new-aduser?view=win10-ps#parameters
# switch parameters for the cmdlet can also be entered with a value $false or $true
}
try {
# '-ErrorAction Stop' ensures that also non-terminating errors get handled in the catch block
New-ADUser #NewUserParms -ErrorAction Stop
}
catch {
# something bad happened. Change 'Write-Warning' into 'throw' if you want your script to exit here
# inside a catch block, the '$_' automatic variable represents the actual exception object.
Write-Warning "Could not create account $username. $($_.Exception.Message)"
}
}
}

Email Password Expiry Reminder to Multiple Users

So, I we have a few users that their computers are not on the domain. One of the annoying things about that is windows will not notify them that their domain password is expired obviously. So I decided I was going to put together a little script using powershell in windows that checks AD to see when their password expires and then if it's about to expire in 3 days to send the user an email to notify them that they should change their password.
I have it set up right now to look at the users distinguished name to pull all the necessary information. but I can only do that for one person, I need to look at two user's distinguished names and send each of them an email when their password is about to expire. I tried creating another $DN variable that I could put the other Distinguished name into and put get-aduser -searchbase $DN, $DN2 but that didn't work for me. Probably was a dumb thing to try, but not sure the syntax needed to accomplish this. Below is my code.
$smtpServer="smtp.office365.com" # Office 365 official smtp server
$expireindays = 100 # number of days for password to expire
$from = # email from
#$logging = "$true" # Set to Disabled to Disable Logging
$logFile = "c:\Scripts\PasswordChangeNotification.csv" # ie. c:\Scripts\PasswordChangeNotification.csv
#$testing = "Disabled" # Set to Disabled to Email Users
$testRecipient =
$date = Get-Date -format ddMMyyyy
$DN = "Distinguished name here"
# Add EMAIL Function
Function EMAIL{
Param(
$emailSmtpServer = $smtpServer, #change to your SMTP server
$emailSmtpServerPort = 587,
$emailSmtpUser = "User"
$emailSmtpPass = "Password", #Password for Send from email account
$emailFrom = "email#domain.com", #Email account you want to send from
$emailTo,
$emailAttachment,
$emailSubject,
$emailBody
)
Process{
$emailMessage = New-Object System.Net.Mail.MailMessage( $emailFrom , $emailTo )
$emailMessage.Subject = $emailSubject
$emailMessage.IsBodyHtml = $true
$emailMessage.Priority = [System.Net.Mail.MailPriority]::High
$emailMessage.Body = $emailBody
$SMTPClient = New-Object System.Net.Mail.SmtpClient( $emailSmtpServer , $emailSmtpServerPort )
$SMTPClient.EnableSsl = $true
$SMTPClient.Credentials = New-Object System.Net.NetworkCredential( $emailSmtpUser , $emailSmtpPass );
$SMTPClient.Send( $emailMessage )
}
}
# Get Users From AD who are Enabled, Passwords Expire and are Not Currently Expired
Import-Module ActiveDirectory
$users = get-aduser -SearchBase $DN -filter * -properties Name, PasswordNeverExpires, PasswordExpired, PasswordLastSet, EmailAddress |where {$_.Enabled -eq "True"} | where { $_.PasswordNeverExpires -eq $false } | where { $_.passwordexpired -eq $false }
$DefaultmaxPasswordAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge
# Process Each User for Password Expiry
foreach ($user in $users)
{
$Name = $user.Name
$emailaddress = $user.emailaddress
$passwordSetDate = $user.PasswordLastSet
$PasswordPol = (Get-AduserResultantPasswordPolicy $user)
# Check for Fine Grained Password
if (($PasswordPol) -ne $null)
{
$maxPasswordAge = ($PasswordPol).MaxPasswordAge
}
else
{
# No FGP set to Domain Default
$maxPasswordAge = $DefaultmaxPasswordAge
}
$expireson = $passwordsetdate + $maxPasswordAge
$today = (get-date)
$daystoexpire = (New-TimeSpan -Start $today -End $Expireson).Days
# Set Greeting based on Number of Days to Expiry.
# Check Number of Days to Expiry
$messageDays = $daystoexpire
if (($messageDays) -ge "1")
{
$messageDays = "in " + "$daystoexpire" + " days."
}
else
{
$messageDays = "today."
}
# Email Subject Set Here
$subject="Your password will expire $messageDays"
# Email Body Set Here, Note You can use HTML, including Images.
$body ="
<p>Dear $name,<br></P><br>
<p>Your domain password will expire $messageDays<br><br>
Please change your password before it expires.<br></P><br><br>
<p>Thanks, <br>
} # End Send Message
} # End User Processing
# End
I am just trying to get some insight on how I could modify my code to use two Distinguished names instead of just the one. I'm sure this isn't the best way to do this, but I'm not too good with coding yet. Hopefully this all makes sense, I appreciate the help!
As you have discovered, you can store the DN values in an array $DNs and process each element of the array. The two expressions inside of the parentheses differ by only the $DN variables that you supply. Using a Foreach loop slightly performs better than piping to ForEach-Object, but in your case it will be negligible.
$users = Foreach ($DN in $DNs) {
get-aduser -SearchBase $DN -filter {
Enabled -eq "True" -and
PasswordNeverExpires -eq "False" -and
passwordexpired -eq "False"
} -properties Name, PasswordNeverExpires, PasswordExpired, PasswordLastSet, EmailAddress)
There are added benefits of doing it this way:
Removal of the Where-Object: Get-ADUser has its own filter as a parameter that can dramatically increase performance over using where in certain queries. It should be faster for you here as the number of returned users from the Get-ADUser query increases.
Got it!!
I changed $DN to:
$DN = "Distinguished name","Distinguished name"
then changed my get-aduser code to:
$users= $DN | ForEach-Objects {get-aduser -SearchBase $PSItem -filter * .....
thanks,

I seem to be getting a positional parameter error with mailmessage in powershell at 144

Below is the code to an internal tool I was put in charge of setting up. It basically references a spreadsheet based on a known value and uses some of that information for sorting purposes and uses other information to populate the automated email. I've had several versions of it working but it seems that for whatever reason it acts like one of the variables isn't populated or is no longer accepting a string as a valid data type. I'm rather new to stack exchange so if there is any formatting or anything I could do to clear up what seems to be the issue, I'll be happy to oblige.
echo off #hiding my stuff
$Setup = Test-Path "$env:APPDATA\ExcelLocation.txt"
Function Get-FileName($initialDirectory)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = "XLSX (*.xlsx)| *.xlsx"
$OpenFileDialog.ShowDialog() | Out-Null
$OpenFileDialog.filename
}
if($Setup -eq $False){
Write-Host "Please participate in 1st time setup!"
$FilePath = Get-FileName "$env:USERPROFILE\Downloads\"
$FilePath | Out-File "$env:APPDATA\ExcelLocation.txt"
$Setup -eq $True
}
$FilePath = Get-Content -Path "$env:APPDATA\ExcelLocation.txt"
Write-Host $FilePath
$DealerCodeLookup = Read-Host -Prompt 'Please put in the Dealer code.'
#Specify the path of the excel file
#$FilePath = "C:\Users\LAB\Downloads\2017 02 Custom Cellular Hierarchy.xlsx"
#Specify the Sheet name
$SheetName = "Sheet1"
# Create an Object Excel.Application using Com interface
$objExcel = New-Object -ComObject Excel.Application
# Disable the 'visible' property so the document won't open in excel
$objExcel.Visible = $false
# Open the Excel file and save it in $WorkBook
$Workbook = $objExcel.Workbooks.Open($FilePath, 2, $True)
# Load the WorkSheet 'BuildSpecs'
$WorkSheet = $WorkBook.sheets.item($SheetName)
#$WorkBook.sheet | Select-Object -Property Name
$Row = 1
$Column = 5
$Found = $False
while (($WorkSheet.Cells.Item($Row, $Column).Value() -ne $Null) -and ($Found -eq $False)) {
#^-- looping though the excel list, updating the row. Stop if Cell is Empty or Value is Found
If (($WorkSheet.Cells.Item($Row, $Column).Value()).ToUpper() -eq $DealerCodeLookup.ToUpper()) {
#^-- Cell value equals $Arg
$locale = $WorkSheet.Cells.Item($Row, $Column+1).Value()
$State =$WorkSheet.Cells.Item($Row, $Column+10).Value()
$Adrs = $WorkSheet.Cells.Item($Row, $Column+7).Value(),$WorkSheet.Cells.Item($Row, $Column+9).Value(),
$WorkSheet.Cells.Item($Row, $Column+10).Value(),$WorkSheet.Cells.Item($Row, $Column+11).Value()
$Found = $True
}
$Row += 1 #Continue to the next row
}
Write-Host $State
$LastRow = $WorkSheet.UsedRange.rows.count + 1
if($Row = $LastRow -and $Found -eq $False){
Write-Host "What you put in is not a valid dealer code!"
$objExcel.quit()
exit
}
$objExcel.quit()
#$DealerCode = Read-Host -Prompt 'Please put in the Dealer code.' #stores the dealer code
$DealerName = 'CUSTOM CELLULAR' #Default dealer name (we are custom cellular)
#$Locale = Read-Host -Prompt 'Please put in the Location name.' #This stores the human location name
#$Address = Read-Host -Prompt 'Please put in the Location Address.' #This stores their address and the thing we use to determine the email address that is sent.
$SoftTokenAmount = Read-Host -Prompt 'Please put in the amount of tokens needed.' #This stores the amount of tokens needed
$Reason = Read-Host -Prompt 'Why do you have to order these tokens??' #This stores the reason for our request
#$SoutheastArea = '* MO *','* KS *','* IL *','* WI *','* MN *','* IA *','* IN *','* NE *' <--possible more efficient usage of the determining loop
#Below is the if statement that determes the Region(SoutheastArea,CenteralArea,WesternArea)
#This specific loop is for the SoutheastArea
if($State -like '*MO*' -or ($State -like '*KS*') -or ($State -like '*IL*') - or ($State -like '*WI*') -or ($State -like '*MN*') -or ($State -like '*IA*')-or ($State -like '*IN*') -or ($State -like '*NE*'))
{
$To = "shalasere1#gmail.com"
}
#This loop is for the CentralArea
Elseif($State -like '*TN*' -or ($State -like '*GA*'))
{
$To = "shalasere2#gmail.com"
}
#This loop is for the WesternArea
Elseif($State -like '*CO*' -or ($State -like '*UT*'))
{
$To = "shalasere3#gmail.com"
}
$From = "tokenrequest#ccinational.com" #Default from email, mine.
#$Cc = "YourBoss#YourDomain.com" #Optional CC'd users to said email (possibly yours.)
$Subject = "Token Request" #The subject of the email
#Below is the default contents of the email including the variables entered above.
$Body = "
New/Replacement Token Request:
Dealer Name: $DealerName
Dealer Code: $DealerCodeLookup
Location: $locale
(office name)
Address: $Adrs
Dealer Email:
Amount of Soft Tokens Needed: $SoftTokenAmount
Reason for Request: $Reason"
#This final chuck is the actual sending an email smtp information for gmail
$SMTPServer = "smtp.office.com"
$SMTPPort = "587"
$secpasswd = ConvertTo-SecureString "publicpass" -AsPlainText -Force
$mycreds = New-Object System.Management.Automation.PSCredential ("TokenRequest#whereiwork.com", $secpasswd)
Send-MailMessage -From $From -to $To -Subject $Subject `
-Body $Body -SmtpServer $SMTPServer -dno onSuccess, onFailure -port $SMTPPort -UseSsl `
-Credential $mycreds
#= New-Object System.Management.Automation.PSCredential ("TokenRequest#whereiwork.com", $secpasswd)
#(new-object System.Net.NetworkCredential("scott","","ccin****.com"))
#(Get-Credential) #<- this line prompts for the outlook login of the default email (mine.) User is jacob#r**** and password is 8888!
Send-MailMessage : A positional parameter cannot be found that accepts argument '='.
At G:\Token Request v2.0.ps1:144 char:1
+ Send-MailMessage -From $From -to $To -Subject $Subject `
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Send-MailMessage], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.SendMailMessage
Seems that it was required to have a single ' around the from subject to be passed correctly.

How to clear value of $msg.to in ps1 script?

I have a script for automaticaly notifying users about AD password expiration. It needed for VPN users. But I can't find a way to solve a problem with $msg.to field. It can't accept, for example, "$msg.to = ''" and works only by $msg.to.add method. It makes not so good situation, when user, who was notified first - will recieve all next e-mails because they will be just added at the end of string, but not replacing all of data in $msg.to
There is a code:
Import-Module ActiveDirectory
#SMTP server name
$smtpServer = "mail.domain.local"
#Creating a Mail object
$msg = new-object Net.Mail.MailMessage
$msgr = new-object Net.Mail.MailMessage
#Creating SMTP server object
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
#E-mail structure
Function EmailStructure($to,$expiryDate,$upn)
{
$msg.IsBodyHtml = $true
$msg.From = "notification#domain.com"
$msg.To.Add($to)
$msg.Subject = "Password expiration notice"
$msg.Body = "<html><body><font face='Arial'>This is an automatically generated message from Exchange service.<br><br><b>Please note that the password for your account <i><u>Domain\$upn</u></i> will expire on $expiryDate.</b><br><br>Please change your password immediately or at least before this date as you will be unable to access the service without contacting your administrator.</font></body></html>"
}
Function EmailStructureReport($to)
{
$msgr.IsBodyHtml = $true
$msgr.From = "notification#domain.com"
$msgr.To.Add($to)
$msgr.Subject = "Script running report"
$msgr.Body = "<html><body><font face='Arial'><pre><b>This is a daily report.<br><br>Script has successfully completed its work.<br>$NotificationCounter users have recieved notifications:<br><br>$ListOfAccounts<br><br></b></pre></font></body></html>"
}
#Set the target OU that will be searched for user accounts
$OU = "OU=Organisation,DC=domain,DC=local"
$ADAccounts = Get-ADUser -LDAPFilter "(objectClass=user)" -searchbase $OU -properties PasswordExpired, extensionAttribute15, PasswordNeverExpires, PasswordLastSet, Mail, Enabled | Where-object {$_.Enabled -eq $true -and $_.PasswordNeverExpires -eq $false}
$NotificationCounter = 0
$ListOfAccounts = ""
Foreach ($ADAccount in $ADAccounts)
{
$accountFGPP = Get-ADUserResultantPasswordPolicy $ADAccount
if ($accountFGPP -ne $null)
{
$maxPasswordAgeTimeSpan = $accountFGPP.MaxPasswordAge
}
else
{
$maxPasswordAgeTimeSpan = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge
}
#Fill in the user variables
$samAccountName = $ADAccount.samAccountName
$userEmailAddress = $ADAccount.ExtensionAttribute15
$userPrincipalName = $ADAccount.UserPrincipalName
if ($ADAccount.PasswordExpired)
{
Write-host "The password for account $samAccountName has expired!"
}
else
{
$ExpiryDate = $ADAccount.PasswordLastSet + $maxPasswordAgeTimeSpan
$TodaysDate = Get-Date
$DaysToExpire = $ExpiryDate - $TodaysDate
$DaysToExpireDD = $DaysToExpire.ToString() -Split ("\S{17}$")
Write-host "The password for account $samAccountName expires on: $ExpiryDate. Days left: $DaysToExpireDD"
if (($DaysToExpire.Days -eq 15) -or ($DaysToExpire.Days -eq 7) -or ($DaysToExpire.Days -le 3))
{
$expiryDate = $expiryDate.ToString("d",$ci)
#Generate e-mail structure and send message
if ($userEmailAddress)
{
EmailStructure $userEmailAddress $expiryDate $samAccountName
$smtp.Send($msg)
Write-Host "NOTIFICATION - $samAccountName :: e-mail was sent to $userEmailAddress"
$NotificationCounter = $NotificationCounter + 1
$ListOfAccounts = $ListOfAccounts + $samAccountName + " - $DaysToExpireDD days left.<br>"
}
}
}
}
Write-Host "SENDING REPORT TO IT DEPARTMENT"
EmailStructureReport("itdepartment#domain.com")
$smtp.Send($msgr)
How can I drop string in $msg.to after each sent e-mail?
If you want to reuse the same message but change the address and send several times to different addresses, use the clear method on the MailAddressCollection.
So your code will look something like this:
$msg.To.Clear()
$msg.To.Add($to)