I'm trying to put a small collection of simple scripts together for PowerShell to make life easier but am having some problems with variables in these scripts.
In a Linux environment I would use a variable in my scripts (usually $1, $2, etc....) like this to make things easier
sed -i 's/$1/$2/g' filename.conf
So that all I would need to do is this
./findreplace.sh old new filename.conf
In powershell, I want to achieve similar, specifically with this command:
Get-ADPrincipalGroupMembership account.name | select name
In this case, the $1 would be where 'user.name' is, so that I would be doing:
.\groups.ps1 user.name
Is there the facility for this?
Groups.ps1 should have the following content:
param( $user )
Get-ADPrincipalGroupMembership $user | Select Name
You can then invoke it as shown, or as .\Groups.ps1 -user user.name.
However, I'd probably try to do it as an "advanced command", and allow it to handle multiple names, and also names passed in via the pipeline:
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline=$true;ValueFromPipelineByPropertyName=$true)]
[Alias("Identity","sAMAccountName")
string[] $Users
)
PROCESS {
ForEach ($User in $Users) {
Get-ADPrincipalGroupMembership $User | Select Name
}
}
which would also allow you to do something like Get-ADUser | .\Groups.ps1
You can add for loop form example script above:
for ($i = 0; $i -lt $args.Length; $i++) { Set-Variable -Scope Script -Name ($i + 1) -Value $args[$i] }
Write-Host "1: $1"
Write-Host "2: $2"
But more easy is just to declare script parameter as:
param($1, $2)
Write-Host "1: $1"
Write-Host "2: $2"
By the way it's bad practice to declare variables as numbers.
Related
I have a script that has a switch, -Add, allowing the addition of 1 role and 1 member (into that role) in a database at a time.
PS> script1.ps1 -Add Database1 role1 member1
PS> script1.ps1 -Add Database1 role1 member2
PS> script1.ps1 -Add Database1 role2 memberx
PS> script1.ps1 -Add Database1 role3
PS> script1.ps1 -Add Database1 role4 membery
It wont be practical to have to run that script everytime if there are more than 1 member or 1 roles/members to add a time. hence, i'd like to update my script with bulk addition of roles/members at once, which would be helpful especially for a TFS use case, in which the user wont have to create multiple releases just to add a couple roles/members for the same database.
i am thinking of implementing a delimiter split, in which for every semicolon, that indicates the start of a new role, and any comma delimited members, all belong to one role unless a semicolon follows (not necessary if its the end of the input, unless it would be hard to achieve something like that with regex?)
pseudocode:
$RoleInput.Split(";") | ForEach {
$role = "$_";
$MemberInput.Split(",") | ForEach {
$member = "$_";
#-Add $DBName $role $member
}
}
ultimately, i would like to achieve similar to the following:
PS> script1.ps1 -Add Database1 role1;role2;role3;role4 member1,member2;memberx;;membery
this means that for role1, member1 and member2 would be added
for role2, memberx is added, for role3, no member is added, and for role 4, membery is added
how would i achieve this correctly with regex to account for whitespaces, and end of input?
Save roles and members in a CSV
"Role","Members"
"role1","member1;member2"
"role2",
"role3","memberX"
then load the CSV using Import-Csv, split the member list at the chosen secondary delimiter (semicolons in the example above), and call your script with those arguments.
Import-Csv 'input.csv' | ForEach-Object {
$role = $_.Role
if ($_.Members) {
$_.Members -split ';' | ForEach-Object { & script1.ps1 -Add $role $_ }
} else {
& script1.ps1 -Add $role
}
}
If you can modify the parameters of your script you could change them to something like this:
[CmdletBinding()]
Param(
[Parameter(ParameterSetName='add', Mandatory=$true)]
[Switch]$Add,
[Parameter(ParameterSetName='add', Mandatory=$true)]
[String]$Role,
[Parameter(ParameterSetName='add', Mandatory=$false)]
[String[]]$Members = #()
)
so that the script accepts a list of members via a named parameter (you also need to adjust how the members are processed in the script, of course), and then call it like this:
Import-Csv 'input.csv' | ForEach-Object {
$params = #{'Role' = $_.Role}
if ($_.Members) { $params['Member'] = $_.Members -split ';' }
& script.ps1 -Add #params
}
This second example uses splatting for passing the parameters to the script.
Early days with CMD and Batch I use the shift command.
How I do that with PowerShell?
Here my sample:
# test.ps1
# start ps script with parameters
param(
[string]$varmon)
if ($varmon) {
foreach ($var in $varmon) {
Write-Host $var
}
}
PS CLI: .\test.ps1 one two three
I get only "one". How can I start the script with more parameters than one?
You could use the automatic variable $args:
# test.ps1
# start ps script with parameters
foreach ($var in $args){
write-host $var
}
Arrays allow you to enter a variable number of arguments - at the end of the day $args is just an automatic array that's created for unassigned variables.
Array parameters are comma delimited, not space - example below.
test.ps1
# test.ps1
# start ps script with parameters
param(
[int[]]$numbers,
[string[]]$names
)
if($numbers){
Write-host "`nYou have entered the following numbers:"
foreach ($num in $numbers){
write-host "Number : $num"
Write-host "Square root: $([system.math]::sqrt($num))"
}
}
if($names){
Write-host "`nYou have entered the following names:"
foreach($name in $names){
Write-host $name
}
}
Example 1: Without using the parameter names, you will need to keep the arrays in order. So $numbers first, and $names second
PS CLI: .\test.ps1 4,9,16 john,jim,jane
Example 1: With parameter names, you can change the order.
PS CLI: .\test.ps1 -names john,jim,jane -numbers 4,9,16
I have a snippet of Powershell where I need to call an external executable, with a list of switches.
& $pathToExe
--project $project `
--server $server `
--apiKey $key `
Now I need to do something like "if $someVariable -eq $True then also add --optionalSwitch $someValue".
How can I do this without a load of repetition? For reference the real exe call is much bigger than this, and the list of optional switches is larger!
How about a hashtable that contains parameters and their values? Like so,
$ht = #{}
$ht.Add('project', 'myProject')
$ht.Add('apikey', $null)
$ht.Add('server', 'myServer')
To build the parameter string, filter the collection by excluding keys without values:
$pop = $ht.getenumerator() | ? { $_.value -ne $null }
Build the command string by iterating the filtered collection
$cmdLine = "myExe"
$pop | % { $cmdLine += " --" + $_.name + " " + $_.value }
# Check the result
$cmdLine
myExe --server myServer --project myProject
I searched and read some topics here but I didn't found what I am looking for.
Basically, I want to check the effective permissions for a specific user for several shares, I want a script such as :
$user = Read-Host "Enter username"
$shares = "\\serverABC\share2","\\serverABC\share1"
foreach ($share in $shares)
{
Cmdlet-EffectivePermissions $share
}
Output expected :
\\serverABC\share1
Full Control : No
Traverse folder / execute / file : YEs
List folder / read data : No
...
\\serverABC\share2"
Full Control : No
Traverse folder / execute / file : YEs
List folder / read data : No
...
In fact, I want to do in Powershell exactly the same way that effective permissions Tab.
Does it exist a built-in solution (without importing any modules, add-ins, ...) with .NET Method (GetUserEffectivePermissions) or with Get-ACL?
I'm not aware of a .NET/PowerShell way to do this natively. There is a PowerShell module here that should be able to do what you're looking for, though. After importing that, you should be able to modify your pseudo code to the following:
$user = Read-Host "Enter username"
$shares = "\\serverABC\share2","\\serverABC\share1"
foreach ($share in $shares) {
Get-EffectiveAccess -Path $share -Principal $user -ListAllRights
}
That returns PS objects instead of simple text. If the format isn't to your liking, you can use some of the utility commands to shape it however you like. Here are two examples of doing that:
First, a simple change to original that doesn't return the exact format you mentioned, but it's pretty close:
foreach ($share in $shares) {
$share
Get-EffectiveAccess -Path $share -Principal $user -ListAllRights | ForEach-Object {
"{0}: {1}" -f $_.Permission, $_.Allowed
}
""
}
Next, a more complicated change that formats the output exactly how you were asking (at least I think):
# Go through each FileSystemRights enum name and add them to a hash table if their value is
# a power of 2. This will also keep track of names that share a value, and later those can
# be combined to provide a friendly permission name
$Ht = #{}
foreach ($Name in [System.Enum]::GetNames([System.Security.AccessControl.FileSystemRights])) {
$Value = [System.Security.AccessControl.FileSystemRights]::$Name
if ($Value.value__ -band ($Value.value__ - 1)) {
# Not a power of 2, so ignore this
continue
}
if (-not $Ht.$Value) {
$Ht.$Value = #()
}
$Ht.$Value += $Name
}
# FullControl isn't a power of 2, but it's useful to test for access, so add it manually
$Ht.([System.Security.AccessControl.FileSystemRights]::FullControl) = "FullControl"
function YesNoTest {
param(
[System.Security.AccessControl.FileSystemRights] $EffectiveAccess,
[System.Security.AccessControl.FileSystemRights] $AccessToTest
)
if (($EffectiveAccess -band $AccessToTest) -eq $AccessToTest) {
"Yes"
}
else {
"No"
}
}
$shares | Get-EffectiveAccess -Principal $user | ForEach-Object {
$_.DisplayName
$EffectiveAccess = $_.EffectiveAccess
$Ht.GetEnumerator() | sort { $_.Key.value__ } -Descending | ForEach-Object {
"{0}: {1}" -f ($_.Value -join " / "), (YesNoTest $EffectiveAccess $_.Key)
}
""
}
Note that this won't be completely accurate if you run this against a remote system and the following conditions are met:
The security descriptor contains groups that are local to the remote system, i.e., non domain groups
The user(s) you're checking is a member of one of the local groups
I want to start a script1.ps1 out of an other script with arguments stored in a variable.
$para = "-Name name -GUI -desc ""this is the description"" -dryrun"
. .\script1.ps1 $para
The args I get in script1.ps1 looks like:
args[0]: -Name name -GUI -desc "this is the description" -dryrun
so this is not what I wanted to get.
Has anyone a idea how to solve this problem?
thx lepi
PS: It is not sure how many arguments the variable will contain and how they are going to be ranked.
You need to use splatting operator. Look at powershell team blog or here at stackoverflow.com.
Here is an example:
#'
param(
[string]$Name,
[string]$Street,
[string]$FavouriteColor
)
write-host name $name
write-host Street $Street
write-host FavouriteColor $FavouriteColor
'# | Set-Content splatting.ps1
# you may pass an array (parameters are bound by position)
$x = 'my name','Corner'
.\splatting.ps1 #x
# or hashtable, basically the same as .\splatting -favouritecolor blue -name 'my name'
$x = #{FavouriteColor='blue'
Name='my name'
}
.\splatting.ps1 #x
In your case you need to call it like this:
$para = #{Name='name'; GUI=$true; desc='this is the description'; dryrun=$true}
. .\script1.ps1 #para
Using Invoke-Expression is another aternative:
$para = '-Name name -GUI -desc "this is the description" -dryrun'
Invoke-Expression -Command ".\script1.ps1 $para"