How to pass multiple domain and local user accounts into a function - function

I have a script that I have been trying to massage and I want to take a loop that was copied multiple times for each user and I want to turn it into a function.
I have figured out to pass multiple local users to the script and I have been able to pass one domain user to the script and have it work successfully.
But I want to be able to create a list of users and their domains (some have none)
and pipe that into the function automatically. I know I could just keep writing out the function with each username and password but If I can avoid that, that would be great
Function Launch-cfm {
Param (
[Parameter(Mandatory=$true, Position=0)]
[string] $username,
[Parameter(Mandatory=$false, Position=1)]
[string] $domain
)
if ($domain -eq $tue) {
Stop-Process -name "autohotkey" -Force -ErrorAction SilentlyContinue
&$OutFile
$user = "$username"
$user_sam = ($members | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}) -contains $user
if ($user_sam -eq $true) {
$user = "$username"
$account = $user
$PassFile = $CredPath+$user+,"_Password.txt"
$keyFile = $CredPath+$user+,".key"
$key = Get-content $keyFile
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $account, (Get-Content $PassFile | ConvertTo-SecureString -Key $key)
Write-Host "info to user about scripts actions."
C:
Start-Process -FilePath $mmcPath -ArgumentList $mscPath -Credential $cred;pause
} else { Write-Host "$user does not exist on this server!!!! Moving on...!
"}
} else {
Stop-Process -name "autohotkey" -Force -ErrorAction SilentlyContinue
&$OutFile
$user = "$username"
$user_sam2 = ($members | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}) -contains $user
if ($user_sam2 -eq $true) {
$account = $domain+,"\"+$user
$PassFile = $CredPath+$user+,"_Password.txt"
$keyFile = $CredPath+$user+,".key"
$key = Get-content $keyFile
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $account, (Get-Content $PassFile | ConvertTo-SecureString -Key $key)
Write-Host "info to user about scripts actions"
Start-Process -FilePath $mmcPath -ArgumentList $mscPath -Credential $cred;pause
} else { Write-Host "$user does not exist on this server!!!! Moving on...!
"}
}
}
$use = "User1","user2"
$dom = "domain1",""
launch-cfm -username $use -domain $dom
any suggestion would be great. or to know if what I am asking is even possible.
Thanks.

What I think you are looking for is a never ending parameter. Give this a try.
Input: Launch-cfm -usernames "Drew","Cleadus","Stack" -domain "SuperDomain1337"
Function Launch-cfm {
Param (
[Parameter(Mandatory=$true)]
[string[]] $usernames,
[Parameter(Mandatory=$false)]
[string] $domain
)
Foreach($user in $usernames){
Do-Magic
}
}
Reasoning:
I am not a fan of positional parameters, throw them where they feel right in the moment.
Using [string[]] instead of [string] means that it will put all values passed to it into an array for later use within the function. This current configuration allows for MULTIPLE users but only ONE domain. You can change that but would need to iterate over each domain and user at a time, unless specified within the script some how.
EG.
Foreach($dom -in $domain){
Foreach($user in $usernames){
Do-Magic
} Else {
Do-LessImpressiveMagic
}
}

Related

Using PowerShell to verify that a specific service exists from a .txt list of server names

I am trying to verify that a specific service exists from a .txt list of server names and then output to a file. In this case I need to also add credentials so I need to use the Invoke-Command. What am I doing wrong here?
clear
start-transcript -path .\Log.txt
$servers = Get-Content .\Resources\Lab.txt
$cred = get-credential domain\Username
$name = Read-Host -Prompt 'Input your service name'
function Getinfo() {foreach($server in $servers)
{
Get-Service | Where-Object { $_.Name -eq $name}-and {$_.Status -eq "Running"}| Format-Table -AutoSize
}
}
Invoke-Command -credential $cred -ComputerName $servers -ScriptBlock ${function:Getinfo}
Stop-Transcript
I think you mean this?
$servers = Get-Content .\Resources\Lab.txt
$cred = Get-Credential domain\Username
$name = Read-Host -Prompt 'Input your service name'
Invoke-Command -Credential $cred -ComputerName $servers -ScriptBlock {
param($name)
Get-Service | Where-Object { $_.Name -eq $name -and $_.Status -eq "Running"}
} -ArgumentList $name | Format-Table -AutoSize

Powershell scripts work when run directly but not when called by another

I have a long list of simple jobs I would like to somewhat automate. It's simple stuff, grab or post info via API and build some reports, nothing fancy.
I decided to build a master script which directs out to a variety of other scripts, each handling its own job. Each one of those little scripts, reference functions from a Utility script which I built that has functions which are common to all the other simple job scripts.
Each of the scripts work perfectly when I run them directly, however, when I try to run them via the master script, which routes to them, they all fail.
One example is that in many cases I need to fetch data from an API but get capped at 1000 object returns when I need 10k+. To solve this, I built a function which recursively calls itself until there is no more data left to collect. Again, this works when called by itself but not from the master script, for some reason, it bails out after the first run (should run 10+ times in this case). Then, it returns nothing.
I am thinking maybe this has something to do with how I am scoping the functions/variables?? Not sure. I have tried scoping to Global, Local & Script but none seem to work. Here's some of the code...
*Master Director Script runs script based on user input*
...
&$choice_hash[$action].script_path
$ScriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
. "$ScriptDirectory\Utilities.psm1"
$user_data = $null
$env_choice = $null
$csv_output_path = $null
$collated_user_data = [System.Collections.ArrayList]#()
function selectEnv {
$global:env_choice = Read-Host #"
> Select an Environment: [Prod] or [Dev]
Your Choice
"#
if ($env_choice -ne 'Prod' -and $env_choice -ne 'Dev') {
consoleCmt $env_choice
consoleCmt 'Invalid Choice. Try again...'
selectEnv
} else {
if ($env_choice -eq 'Prod') {
$global:csv_output_path = '\\etoprod\******\Exports\Report_Users_Prod.csv'
} else {
$global:csv_output_path = '\\etoprod\******\Exports\Report_Users_Dev.csv'
}
$global:user_data = process_data $env_choice 'api/xm/1/people?embed=roles&limit=1000'
}
}
function processUsersData {
foreach($user in $user_data) {
$user_roles = ''
$role_divider = ','
for($i = 0; $i -lt $user.roles.data.length; $i++) {
# Only append a comma if there are more, otherwise leave blank for CSV deliniation
if ($i -eq $user.roles.data.length - 1) {
$role_divider = ''
}
$user_roles += $user.roles.data[$i].name + $role_divider
}
# Build ordered hash table with above data
$sanatized_user = [pscustomobject][ordered]#{id = $user.targetName; firstName = $user.firstName; lastName = $user.lastName; siteName = $user.site.name; roles = $user_roles }
# Shovel into storage array used for building the CSV
$global:collated_user_data += $sanatized_user
}
}
notice 'Initiating Groups Report Script'
selectEnv
processUsersData
exportCsv $collated_user_data $csv_output_path
Utility Script (relevant functions being called)
$res = $null
$content = #()
...
function process_data($env, $url) {
fetch_data $env $url
foreach($i in $res.data) {
$global:content += $i
}
if($res.links.next) {
fetch_more $env $res.links.next
}
return $content **Should return full collection of data, but fails after one pass**
}
function fetch_data($env, $url) {
$base = generateEnvBase $env
$path = "$base/$url"
$req = Invoke-WebRequest -Credential $cred -Uri $path -Method GET
$global:res = ConvertFrom-Json $req
}
function fetch_more($env, $url) {
$base = generateEnvBase $env
$path = "$base$url"
$req = Invoke-WebRequest -Credential $cred -Uri $path -Method GET
$res = ConvertFrom-Json $req
foreach($i in $res.data) {
$global:content += $i
}
if($res.links.next) {
fetch_more $env $res.links.next
}
}
Sorry in advance if I have not followed procedure or etiquette, I'm new here.
This should work if you declare all variables in Main.ps1 that are needed by functions. You could also use the "Script" scope when creating a new variable inside a function that you want to use outside the function. Example $Script:Var = "Stuff" created inside a function will be available to whole script.
Directory Structure
C:\Script\Root
| Main.ps1
\---Utilities
fetch_data.ps1
fetch_more.ps1
processUsersData.ps1
process_data.ps1
selectEnv.ps1
Main.ps1
#---[ Initization ]---#
# Strings
[String]$RootPath = $PSScriptRoot
[String]$UtilPath = "$($RootPath)\Utilities"
[String]$env_choice = $null
[String]$csv_output_path = $null
# Arrays
[Array]$user_data = #()
[Array]$content = #()
[Array]$collated_user_data = #()
[Array]$res = #()
#---[ Source in Utilities ]---#
# Get the scripts
$Utilities = Get-ChildItem -Path "$UtilPath" -File | Where-Object {$_.Extension -eq ".ps1"}
# Source in each one
foreach ($Item in $Utilities) {
.$Item.FullName
}
#---[ Select an Environment ]---#
# Get the User's choice
$env_choice = selectEnv
# Process the choice
switch ($env_choice) {
Prod {
$csv_output_path = '\\etoprod\******\Exports\Report_Users_Prod.csv'
$user_data = process_data 'Prod' 'api/xm/1/people?embed=roles&limit=1000'
}
Dev {
$csv_output_path = '\\etoprod\******\Exports\Report_Users_Dev.csv'
$user_data = process_data 'Dev' 'api/xm/1/people?embed=roles&limit=1000'
}
Test {
Write-Output "Test is not an option. Choose wisely."
exit 1
}
Default {
Write-Output "Unknown Environment Choice."
exit 1
}
}
#---[ Process Users and Export ]---#
processUsersData
exportCsv $collated_user_data $csv_output_path
selectEnv.ps1
function selectEnv {
$Title = "Environment:"
$Info = "Please choose an environment"
# Options
$Prod = New-Object System.Management.Automation.Host.ChoiceDescription '&Prod', 'Production environment'
$Dev = New-Object System.Management.Automation.Host.ChoiceDescription '&Dev', 'Development environment'
$Test = New-Object System.Management.Automation.Host.ChoiceDescription '&Test', 'Testing environment'
$Options = [System.Management.Automation.Host.ChoiceDescription[]]($Prod, $Dev, $Test)
$Default = 0
# Promp the User
$Choice = $host.UI.PromptForChoice($Title , $Info , $Options, $Default)
$Result = $Options[$Choice].Label -Replace '&',''
return $Result
}

powershell variable into where-object does not return data

I am writing a script to ultimately check a block of servers for a certificate by FriendlyName and then go back and delete them once confirmed. Right now I am just trying to get the initial check to work. Currently it is not returning any data. Can anyone help?
$ContentsPath = "C:\Servers.txt"
$Servers = Get-Content $ContentsPath
$CertDeletionFile = "C:\CertsDeleted.csv"
$Today = Get-Date
$Certificate = Read-Host -Prompt "What certificate would you like to
REMOVE?"
write-host $Certificate
function findCert {
param ([string]$Certificate)
Invoke-Command -ComputerName $Servers -ScriptBlock {Get-Childitem -Path
Cert:LocalMachine\My | where {$_.friendlyname -eq $Certificate } | Select-
Object -Property FriendlyName }
}
findCert
As Mathias R. Jessen comments, your findcert function needs a certificate name as a parameter, and you aren't passing anything when you call it, so it won't run properly.
You're also trying to use a local computer variable $Certificate, on a remote computer inside an invoke-command, and the remote computer can't get to that variable across the remoting.
I've rewritten it, with $using: which is a syntax that tells PS to send the value over the remoting session, and with renamed variables so it's more clear which part is accessing which variables:
$ContentsPath = 'C:\Servers.txt'
$Servers = Get-Content -LiteralPath $ContentsPath
$CertDeletionFile = 'C:\CertsDeleted.csv'
$Today = Get-Date
$typedCertificateName = Read-Host -Prompt "What certificate would you like to
REMOVE?"
write-host $typedCertificateName
function findCert {
param ([string]$Certificate)
Invoke-Command -ComputerName $Servers -ScriptBlock {
Get-Childitem -Path Cert:LocalMachine\My |
where-Object {$_.friendlyname -eq $using:Certificate } |
Select-Object -Property FriendlyName
}
}
findCert -Certificate $typedCertificateName

try-catch bypassing a step

I have a script that tests connection to a list of servers, and if contactable, gets the status of a service, and puts the results into three variables, $Computer, $Ping (True/False), and $Service (Running or Stopped).
The output is in a hashtable but I can only get to show the servers that ARE contactable, and not the ones that cannot be contactable.
I have placed a try/catch in the $Ping block, as well as -ErrorAction Stop, so that it doesn't attempt to run the $Service script, and instead go to the next $Computer in the array. I think I am trying to do two things at once that are conflicting each other:
add the variables to the #Splat and
don't process any further.
There are actually many more remote registry queries in my script, which will be irrelevant if the $Computer cannot be contactable, but I have shortened it for this post.
Function Get-Ping {
$Servers = (gc "c:\temp\test.txt")
foreach ($Computer in $Servers) {
Write-Host
Write-Host "---------------------------------"
Write-Host "QUERYING $Computer"
Write-Host
Write-Host "Performing ping test..."
try {
$Ping = Test-Connection $Computer -Count 1 -ErrorAction Stop
} catch {
Write-Warning "Cannot Ping $Computer"
Write-Host "Trying next computer..."
Write-Host
continue
}
if ($Ping) {$Ping="$True"}
Write-Host $Computer "can be pinged"
$svcRRStopped = $false
if ($Computer -ne $env:COMPUTERNAME) {
Write-Host "Check RemoteRegistry status..."
}
$svcRR = Get-Service -ComputerName $Computer -Include RemoteRegistry
$SelectSplat = #{
Property = (
'Computer',
'Ping',
'Service'
)}
New-Object -TypeName PSObject -Property #{
Computer=$Computer
Ping=$Ping
Service=$svcRR.status
} | Select-Object #SelectSplat
}
}
$results = Get-Ping
$tableFragment = $results | Select 'Computer','Ping','Service'
$tableFragment
Don't make things more complicated than they need to be.
function Get-Ping {
Get-Content 'C:\temp\test.txt' | ForEach-Object {
$isAvailable = [bool](Test-Connection $_ -Count 1 -EA SilentlyContinue)
if ($isAvailable) {
$rreg = Get-Service -Computer $_ -Name RemoteRegistry |
Select-Object -Expand Status
} else {
$rreg = 'n/a'
}
New-Object -Type PSObject -Property #{
Computer = $_
Ping = $isAvailable
Service = $rreg
}
}
}
Get-Ping
You can simply use the -Quiet Parameter:
Test-Connection $_ -Count 1 -Quiet

Mandatory and default parameters of a function

I have written a little PowerShell send-email-function for a special purpose (error-message) so that the from and to addresses are always the same!
Function Send-EMail {
Param (
[Parameter(Mandatory=$true)] [String]$EmailTo = "ToAddr#gmx.at", # default
[Parameter(Mandatory=$true)] [String]$EmailFrom = "FromAddr#gmail.com", #default
[Parameter(Mandatory=$true)] [String]$Subject,
[Parameter(Mandatory=$true)] [String]$Body,
[Parameter(mandatory=$false)] [String]$Attachment,
[Parameter(mandatory=$true)] [String]$Password
)
$SMTPServer = "smtp.gmail.com"
$SMTPMessage = New-Object System.Net.Mail.MailMessage($EmailFrom,$EmailTo,$Subject,$Body)
if ($attachment -ne $null) {
$SMTPattachment = New-Object System.Net.Mail.Attachment($attachment)
$SMTPMessage.Attachments.Add($STMPattachment)
}
$SMTPClient = New-Object Net.Mail.SmtpClient($SmtpServer, 587)
$SMTPClient.EnableSsl = $true
$SMTPClient.Credentials = New-Object System.Net.NetworkCredential($EmailFrom.Split("#")[0], $Password);
$SMTPClient.Send($SMTPMessage)
Remove-Variable -Name SMTPMessage
Remove-Variable -Name SMTPClient
Remove-Variable -Name Password
Remove-Variable -Name Body
Remove-Variable -Name Subject
} #End Function Send-EMail
....
$subj = "Subject"
$body = #" Body-Text "#
Send-EMail -Subject $subj -Body $body -Password "myPWD" -Attachment $logFile
I expect now that I don't have to specify again the email address, but if run it line by line in the ISE debugger a little window is opened asking me for the EmailTo address:
What do I have to change so that I am not asked for the already given addresses?
The Mandatory parameter attribute flag:
[Parameter(Mandatory=$true)]
really means "It is mandatory for the caller to supply an argument to this parameter".
If you want a parameter to fall back to a default value that you provide in the param block, set the Mandatory flag to $false:
[Parameter(Mandatory=$false)]
[string]$EmailTo = "to#company.domain",
This may seem a little counter-intuitive, but it allows you to detect when a user didn't supply a parameter that is needed:
if(-not($PSBoundParameters.ContainsKey('EmailTo')) -and $EmailTo)
{
# User relied on default value
}