My Powershell script has some features/functions implemented, but there are some features that I want to restrict to some users.
In order to allow the current user or a different user to select such restricted features from the menu, I am looking for a way to require user authentication from Windows to continue. How could I do that? How the UserAuthentication function below should be like?
Code:
$feature = Read-Host 'Select the feature by typing the number [1 - 2]'
switch ($feature)
{
1
{
write-output "This feature any user can reach"
}
2
{
$user = Read-Host "This feature only some user can reach and requires authentication. Entry your username to proceed"
$allowedUsers = "user1", "user2"
if($allowedUsers.contains($user))
{
write-output "This feature the user $user can reach. Please authenticate to continue"
if((UserAuthentication $user) -eq $true)
{
write-output "$user successfully authenticated"
}
else
{
write-output "$user unsuccessful authenticated"
}
}
else
{
write-output "This feature the user $user cannot reach"
}
}
}
function UserAuthentication($user)
{
return $true #May return 'True' if successfully authenticated or 'False' if not.
}
This answer is for when your users are member of an AD domain
I have changed the function name UserAuthentication to Get-Authentication to comply with the Verb-Noun function naming convention in PowerShell.
# helper function test if a username/password combination is valid.
# if valid, the username entered in the box is returned.
function Get-Authentication {
$Credentials = Get-Credential "$env:USERDOMAIN\$env:USERNAME" -Message "Please authenticate to continue" -ErrorAction SilentlyContinue
if ($Credentials) {
$UserName = $Credentials.UserName
$Password = $Credentials.GetNetworkCredential().Password # --> plain-text password
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ds = New-Object System.DirectoryServices.AccountManagement.PrincipalContext Domain
if ($ds.ValidateCredentials($UserName, $Password)) {
# return the username entered
$UserName
}
}
}
# your code here
# fill in the SamAccountNames of allowed users for this feature
$allowedUsers = 'samaccountname','of', 'users', 'that', 'are', 'allowed', 'to', 'use', 'feature 2'
$feature = Read-Host 'Select the feature by typing the number [1 - 2]'
switch ($feature) {
'1' { Write-Output "This feature any user can reach" }
'2' {
$user = Get-Authentication
if ($null -ne $user -and $allowedUsers -contains $user) {
Write-Output "User $user is allowed for this feature"
}
else {
Write-Output "This feature the user cannot reach"
}
}
}
Related
I've created a PS script in the past and I would like it to be more interactive.
This is the script I've created.
$csv = Import-CSV 'C:\Users\w0vlnd\Desktop\Powershells\Computers in a specific workgroup or domain.csv'
$ADprops = #(
'DisplayName'
'Mail'
'LastBadPasswordAttempt'
'AccountExpirationDate'
'PasswordLastSet'
'Enabled'
)
$filter = #(
$ADprops
#{
Name = 'Account active'
Expression = {$Account}
}, #{
Name = 'Password Changeable'
Expression = { $changeable }
}, #{
Name = 'Password Expires'
Expression = { $expires }
}, #{
Name = 'Last logon'
Expression = { $lastlogon }
}, #{
Name = 'PC Name'
Expression = { $pcName }
}, #{
Name = 'System Boot Time'
Expression = { $Boot }
}
)
Clear-Host
do
{
Write-Host "Enter the user ID: " -ForegroundColor Cyan -NoNewline
$UserName = Read-Host
Write-Host ""
#Computer Info#
$pcName = $csv.Where({ $_."User ID" -match $Username })."PC Name"
$boot = SystemInfo /s $pcName | Select-String -Pattern 'System Boot Time'|
ForEach-Object { ($_ -split '\s{2,}')[1] }
#Account and passowrd information#
$Account, $expires, $changeable, $lastlogon = net user $Username /domain |
Select-String -Pattern 'Account active|Password Changeable|Password Expires|Last logon'|
ForEach-Object { ($_ -split '\s{2,}')[1] }
Get-ADUser -Identity $Username -Properties $ADprops |
Select-Object $filter
} while ($Username -notcontains $Processes)
And I want to put it in to this script but I'm figuring out how. As in the first script the user must first fill in the user ID. Afterwards he get should get the options U,P,... Each option should be executing a command. An example of which command you can find in the first script like: #computer info# IF I haver already one command running I'll figure the rest out myself.
How can I do this? Also the menu should come back after the option has been chosen.
Thanks in advance!
param (
[string]$Title = 'UserInfo V3.1'
)
Clear-Host
Write-Host "================ $Title ================"
Write-Host "Press 'U' for general user info."
Write-Host "Press 'P' for account and password information."
Write-Host "Press 'C' computer information."
Write-Host "Press 'G' group memberships."
Write-Host "Press 'R' search for other user ID."
Write-Host "Press 'Q' to Quit."
}
Show-Menu –Title 'UserInfo V3.1'
$selection = Read-Host "Please make a selection"
switch ($selection)
{
'U' {
'You chose option #U'
} 'P' {
'You chose option #P'
} 'C' {
'You chose option #C'
} 'G' {
'You chose option #G'
} 'Q' {
'You chose option #Q'
return
}
I would use a PromptForChoice method, if a user select a wrong option, question will be asked again :
$repeat = $true
while ($repeat)
{
[System.Management.Automation.Host.ChoiceDescription[]]$choicelist =
"&User infos",
"&Account and password infos",
"&Computer infos",
"&Group membership",
"&Search for another user",
"&Quit"
switch ($Host.UI.PromptForChoice("Please select", "What do you want ?", $choicelist, 0))
{
0 {
"User infos here"
break
}
1 {
"Account & password infos here"
break
}
2 {
"Computer infos here"
break
}
3 {
"Group membership infos here"
break
}
4 {
"Search for another user Id here"
break
}
5 {
"Exiting."
$repeat = $false
}
}
}
Notice the & sign before the letter to be used (I have not used same letters as your code). This sign could be used inside the choice text, for example "Account and &password infos" means that you need to use the letter p to select this option.
The value returned by PromptForChoice is the selected index of the choice array.
I have a script that uses an input file with users SamAccountName. It goes thought the script and updates the users profile to check mark the
"Deny this user permissions to log on to Remote Desktop Session Host Server".
Once the value is set I also want to read it to make sure it got changed properly.
For most users it works but for some it will say it changed but it actually is not.
$ADUsers = Get-Content "C:\PSTOOLS\Passwordneverexpire\disable-enbale deny logon.txt"
ForEach ($User in $ADUsers) {
$UserDN = (Get-ADUser -identity $User).distinguishedName
if ($UserDN) {
$Command = [ADSI] "LDAP://$UserDN"
$Command.psbase.invokeSet("AllowLogon",0) # it will NOT allow remote logon
$Command.setinfo()
Write-Host $User + "Allow logon " + $Command.psbase.InvokeGet("AllowLogon") -as [Boolean]
}
else {
Write-Host "$UserDN Does Not Exist"
}
}
AllowLogon has 3 state possible :
Enabled = $true
Disabled = $False
Never Defined = ERROR on read !
you can use this code
$AdsiItem = [adsi]"LDAP://$($item.DistinguishedName)"
try {
$AllowLogon = $AdsiItem.InvokeGet("AllowLogon")
} catch {
# unreadable, because it is never defined
$AllowLogon = $Null
}
if($AllowLogon -eq $true) {
'TSE authorized!'
} elseif ($null -eq $AllowLogon ) {
'TSE not forbidden!'
} else {
'Forbidden!'
}
I'm trying to create a menu for tasks i run in 365 Exchange. I want the script to return to the main menu once rather then ending when i run it. any guidance would be greatly received
edit - thanks it now loops back to the menu, could you point me in the direction of displaying a the powershell results before heading back to the main menu.
You should properly format your question, the code is unreadable.
A general approach can be like this:
do
{
Show-Menu
$inputChoice = Read-Host "Select your choice"
switch ($inputChoice)
{
'1' {
clear-host
write-Host "The output will be shown onscreen"
get-exomailbox -resultsize 10
pause
} '2' {
write-Host "2"
} 'q' {
return
}
}
}
until ($inputChoice -eq 'q')
Continuing from my comment to highlight come suggestions.
I do not have an M365 instance to test this in, so that side is untested, but this is just to give you another take on your use case approach. Ensure you leverage -WhatIf or -Confirm on those cmdlets to make sure of stuff before you really run things.
Output to the screen is the PowerShell default for text. So, I have a standard of only using Write-* only when it's absolutely needed. Others have their own
take of course.
I am using scriptblock to allow for reusable code to eliminate redundancy. The return to the menu and out of the screen is also in this.
Again, this is just another take on what you've done and what others have noted.
Clear-Host
do
{
Add-Type -AssemblyName System.Drawing,
PresentationCore,
PresentationFramework,
System.Windows.Forms,
Microsoft.VisualBasic
[System.Windows.Forms.Application]::EnableVisualStyles()
$MessageBox = [Microsoft.VisualBasic.Interaction]
$UserEmalAddreess = {
$MessageBox::InputBox(
'Enter Email Address of identity',
'User',
$env:UserName
)
}
$UserEmalAddreessAccess = {
$MessageBox::InputBox(
'Enter Email Address of person who gains/revokes access',
'User',
$env:UserName
)
}
$AddMailboxPermission = {
$AddMailboxPermissionSplat = #{
Identity = (& $UserEmalAddreess)
InheritanceType = 'All'
User = $(& $UserEmalAddreessAccess)
AccessRight = 'FullAccess'
}
Add-MailboxPermission #AddMailboxPermissionSplat
}
$BannerLine = $(('=') * 16)
function Show-Menu
{
param
(
[string]$Title = '365 Exchange Admin'
)
Clear-Host
"$BannerLine $Title $BannerLine"
'1: Connect to PS Exchange Online'
'2: Give Access To MailBox - No AutoMapping'
'3: Give Access To MailBox - With AutoMapping'
'4: Get MailBox Permissions'
'5: Remove MailBox Permissions'
'6: Create A Shared MailBox'
'7: Convert To Shared MailBox'
'8: Get Mailbox Info'
'9: Set New 365 Password'
"Q: Press 'Q' to quit."
}
Show-Menu
$selection = Read-Host Please make a selection
switch ($selection)
{
'1'
{
$MFAExchangeModule = (
(Get-ChildItem -Path $("$env:LOCALAPPDATA\Apps\2.0\") -Filter 'CreateExoPSSession.ps1' -Recurse).FullName |
Select-Object -Last 1
)
. $MFAExchangeModule
Connect-EXOPSSession
}
'2'
{
& $AddMailboxPermission -Automapping $false |
Out-Host
}
'3'
{
& $AddMailboxPermission -Automapping $true |
Out-Host
}
'4'
{
Get-MailboxPermission -Identity $(& $UserEmalAddreess) |
Format-List
}
'5'
{
$RemoveMailboxPermission = #{
Identity = $(& $UserEmalAddreess)
User = $(& $UserEmalAddreessAccess)
AccessRights = 'FullAccess'
InheritanceType = 'All'
}
Remove-MailboxPermission #RemoveMailboxPermission |
Out-Host
}
'6'
{
New-Mailbox -Name $(& $UserEmalAddreess) -Shared |
Out-Host
}
'7'
{
Set-Mailbox $(& $UserEmalAddreess) -Type Shared |
Out-Host
}
'8'
{
Get-Mailbox -Identity $(& $UserEmalAddreess) |
Format-Table Name, RecipientTypeDetails
}
'9'
{
$Creds = Get-Credential -Credential "$env:UserName#$env:USERDNSDOMAIN"
$setMsolUserPasswordSplat = #{
UserPrincipalName = $Creds.UserName
NewPassword = $Creds.GetNetworkCredential().Password
ForceChangePassword = $False
}
Set-MsolUserPassword #setMsolUserPasswordSplat |
Out-Host
}
'Q' {return}
}
}
until ($inputChoice -eq 'q')
I am attempting to make a script that will check a variety of settings and ask permission before changing them. I'm writing this for non-IT users, so each of the user checks is a pop out message box. I thought that this part of the code was working, but now not so much. I'm getting an error indicating the user wasn't found, but that's what I'm trying to use as an if-else trigger. Error Message
As always, thanks to all the saints out there saving my behind from this.
PS: I know its not overly secure to have the password hard coded into the script, and if it were up to me, we wouldn't do it this way. But this is how the boss wants it for now.
#User Group
If (Get-LocalGroup -Name "Koko Svc"){
Write-Host "User Group Koko Svc already exists"
}
Else{
$UserConfirm=[System.Windows.Forms.MessageBox]::Show("Would you like to create the Local Group `"Koko Svc`"?","`"Koko Svc`" Group Not Found",[System.Windows.Forms.MessageBoxButtons]::YesNoCancel)
switch ($UserConfirm){
"Yes" {
write-host "User agreed to create `"Koko Svc`""
New-LocalGroup -Name 'Koko Svc' -Description 'KoKo Svc'
}
"No" {
write-host "User declined to create `"Koko Svc`""
break
}
"Cancel" {
write-host "User stopped Settings Check"
exit
}
}
}
#Users
If (Get-LocalUser -Name "KoKo Svc"){
Write-Host "User Koko Svc already exists"
}
Else{
$UserConfirm=[System.Windows.Forms.MessageBox]::Show("Would you like to create the User `"Koko Svc`"?","`"Koko Svc`" User Not Found",[System.Windows.Forms.MessageBoxButtons]::YesNoCancel)
switch ($UserConfirm){
"Yes" {
write-host "User agreed to create `"Koko Svc`""
$Password1 = ConvertTo-SecureString "FooBar" -AsPlainText -Force
New-LocalUser "KoKo Svc" -Password $Password1 -FullName "KoKo Svc"
Add-LocalGroupMember -Group 'Administrators' -Member ('KoKo Svc','Administrators')
Add-LocalGroupMember -Group 'Koko Svc' -Member ('KoKo Svc','KoKo Svc')
}
"No" {
write-host "User declined to create `"Koko Svc`""
break
}
"Cancel" {
write-host "User stopped Settings Check"
exit
}
}
}
If (Get-LocalUser -Name "Valued Customer"){
Write-Host "User Valued Customer already exists"
}
Else{
$UserConfirm=[System.Windows.Forms.MessageBox]::Show("Would you like to create the User `"Valued Customer`"?","`"Valued Customer`" User Not Found",[System.Windows.Forms.MessageBoxButtons]::YesNoCancel)
switch ($UserConfirm){
"Yes" {
write-host "User agreed to create `"Valued Customer`""
$Password2 = ConvertTo-SecureString "BarFoo" -AsPlainText -Force
New-LocalUser "Valued Customer" -Password $Password2 -FullName "Valued Customer"
Add-LocalGroupMember -Group 'Administrators' -Member ('Valued Customer','Administrators')
Add-LocalGroupMember -Group 'Koko Svc' -Member ('Valued Customer','KoKo Svc')
}
"No" {
write-host "User declined to create `"Valued Customer`""
break
}
"Cancel" {
write-host "User stopped Settings Check"
exit
}
}
}
You could change it to
If (Get-LocalUser -Name "Valued Customer" -ErrorAction SilentlyContinue){
Write-Host User Valued Customer already exists
}
Else{
write-host available
}
That way only if it produces an output it will run the if portion.
A few things.
You can't use the same name for both a group and a user, which is why you get the error
The name KoKo Svc is already in use
On commands that you expect to fail, and want to take action if it does, use try/catch blocks. You can go the -ErrorAction SilentlyContinue route, but in my opinion, that's sloppy and inelegant. You'll just be ignoring the error, instead of taking action on it. I would do something like this
try
{
Get-LocalUser -Name $Name -ErrorAction Stop
$exists = $true
Write-Host "User $Name already exists"
}
catch [Microsoft.PowerShell.Commands.UserNotFoundException]
{
$exists = $false
}
then DRY the code up by tossing it in a simple function or two, so I'm not copy pasting a big chunk every time
function Test-LocalUser([string]$Name)
{
try
{
Get-LocalUser -Name $Name -ErrorAction Stop
$exists = $true
Write-Host "User $Name already exists"
}
catch [Microsoft.PowerShell.Commands.UserNotFoundException]
{
$exists = $false
}
return $exists
}
function Test-LocalGroup([string]$Name)
{
try
{
Get-LocalGroup -Name $Name -ErrorAction Stop
$exists = $true
Write-Host "Group $Name already exists"
}
catch [Microsoft.PowerShell.Commands.GroupNotFoundException]
{
$exists = $false
}
return $exists
}
Then for your code, you can do something like this
$group = "Koko Svc group"
if(!(Test-LocalGroup -Name $group))
{
$UserConfirm=[System.Windows.Forms.MessageBox]::Show("Would you like to create the Local Group `"$group`"?","`"$group`" Group Not Found",[System.Windows.Forms.MessageBoxButtons]::YesNoCancel)
switch ($UserConfirm)
{
"Yes"
{
write-host "User agreed to create `"$group`""
New-LocalGroup -Name $group -Description $group
}
"No"
{
write-host "User declined to create `"$group`""
break
}
"Cancel"
{
write-host "User stopped Settings Check"
exit
}
}
}
#Users
$user = "Koko Svc user"
if(!(Test-LocalUser -Name "Koko Svc"))
{
$UserConfirm=[System.Windows.Forms.MessageBox]::Show("Would you like to create the User `"$user`"?","`"$user`" User Not Found",[System.Windows.Forms.MessageBoxButtons]::YesNoCancel)
switch ($UserConfirm)
{
"Yes"
{
write-host "User agreed to create `"$user`""
$Password1 = ConvertTo-SecureString "FooBarasdasdasdasdadad2334342342!!!" -AsPlainText -Force
New-LocalUser "$user" -Password $Password1 -FullName "$user"
Add-LocalGroupMember -Group 'Administrators' -Member ($user,'Administrators')
Add-LocalGroupMember -Group $group -Member ($user,$group)
}
"No"
{
write-host "User declined to create `"$user`""
break
}
"Cancel"
{
write-host "User stopped Settings Check"
exit
}
}
}
I would also recommend tossing the user creation portion into a function as well, to reduce all the copy-paste code. If this is something designed for other people, making it easy to maintain is a priority. By making functions out of your repeated code, you only have to make a change in one place, rather than every copy/pasted section. If you want to see an example of the user creation portion in a function, lemme know and I'll toss it in an edit.
I'm trying to write a script to remotely rename multiple computers. Here's what I have (I know the Verify function works so that can be skipped over. The issue is occurring with the GetComputers function)
function main{
$DomainCredential = Verify
$computers = GetComputers
#Rename -computers $computers -DomainCredential $DomainCredential
}
function Verify{
# Prompt for Credentials and verify them using the DirectoryServices.AccountManagement assembly.
Write-Host "Please provide your credentials so the script can continue."
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
# Extract the current user's domain and also pre-format the user name to be used in the credential prompt.
$UserDomain = $env:USERDOMAIN
$UserName = "$UserDomain\$env:USERNAME"
# Define the starting number (always #1) and the desired maximum number of attempts, and the initial credential prompt message to use.
$Attempt = 1
$MaxAttempts = 5
$CredentialPrompt = "Enter your Domain account password (attempt #$Attempt out of $MaxAttempts):"
# Set ValidAccount to false so it can be used to exit the loop when a valid account is found (and the value is changed to $True).
$ValidAccount = $False
# Loop through prompting for and validating credentials, until the credentials are confirmed, or the maximum number of attempts is reached.
Do {
# Blank any previous failure messages and then prompt for credentials with the custom message and the pre-populated domain\user name.
$FailureMessage = $Null
$Credentials = Get-Credential -UserName $UserName -Message $CredentialPrompt
# Verify the credentials prompt wasn't bypassed.
If ($Credentials) {
# If the user name was changed, then switch to using it for this and future credential prompt validations.
If ($Credentials.UserName -ne $UserName) {
$UserName = $Credentials.UserName
}
# Test the user name (even if it was changed in the credential prompt) and password.
$ContextType = [System.DirectoryServices.AccountManagement.ContextType]::Domain
Try {
$PrincipalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext $ContextType,$UserDomain
} Catch {
If ($_.Exception.InnerException -like "*The server could not be contacted*") {
$FailureMessage = "Could not contact a server for the specified domain on attempt #$Attempt out of $MaxAttempts."
} Else {
$FailureMessage = "Unpredicted failure: `"$($_.Exception.Message)`" on attempt #$Attempt out of $MaxAttempts."
}
}
# If there wasn't a failure talking to the domain test the validation of the credentials, and if it fails record a failure message.
If (-not($FailureMessage)) {
$ValidAccount = $PrincipalContext.ValidateCredentials($UserName,$Credentials.GetNetworkCredential().Password)
If (-not($ValidAccount)) {
$FailureMessage = "Bad user name or password used on credential prompt attempt #$Attempt out of $MaxAttempts."
}
}
# Otherwise the credential prompt was (most likely accidentally) bypassed so record a failure message.
} Else {
$FailureMessage = "Credential prompt closed/skipped on attempt #$Attempt out of $MaxAttempts."
}
# If there was a failure message recorded above, display it, and update credential prompt message.
If ($FailureMessage) {
Write-Warning "$FailureMessage"
$Attempt++
If ($Attempt -lt $MaxAttempts) {
$CredentialPrompt = "Authentication error. Please try again (attempt #$Attempt out of $MaxAttempts):"
} ElseIf ($Attempt -eq $MaxAttempts) {
$CredentialPrompt = "Authentication error. THIS IS YOUR LAST CHANCE (attempt #$Attempt out of $MaxAttempts):"
}
}
} Until (($ValidAccount) -or ($Attempt -gt $MaxAttempts))
# If the credentials weren't successfully verified, then exit the script.
If (-not($ValidAccount)) {
Write-Host -ForegroundColor Red "You failed $MaxAttempts attempts at providing a valid user credentials. Exiting the script now... "
EXIT
} Else {
Write-Host "Credntials authenticated"
return $Credentials
}
}
function GetComputers{
$oldnames = New-Object System.Collections.ArrayList
Write-Output "Enter the PC numbers to be named. Do not include 'PC' only type the following numbers. Type 'end' when finished"
$userinput = Read-Host
while($userinput -ne "end"){
$userinput = "$('PC')$($userinput)"
[void]$oldnames.Add($userinput)
$userinput = Read-Host
}
return $oldnames
}
workflow Rename($computers, $DomainCredential){
foreach -parallel ($computer in $computers){
$newname = "$($computer)$('MK')"
Rename-Computer -PSComputerName $computer -NewName $newname -DomainCredential $DomainCredential
}
}
main
The Verify function works perfectly, but then it just hangs and nothing else happens. I added a debug line between the call of the Verify and the call of the GetComputer functions, and that also printed out. Im new to powershell and am out of ideas
Are you certain that it is hanging, or is Read-Host just showing a blank input location? Calling that function without any arguments will just give you a blinking cursor in command line. Try adding some form of prompt to read-host, like below:
PS C:\Users\mbolton> $var=read-host
"string"
PS C:\Users\mbolton> $var
"string"
PS C:\Users\mbolton> $var=read-host "type something in"
type something in: "different string"
PS C:\Users\mbolton> $var
"different string"
PS C:\Users\mbolton>