I need to work on a fairly complicated script that involves multiple service accounts to different APIs and for the most part, the script works fine but I'm running into an issue that's bugging me like crazy. I can't run the script as a different user because the powershell running the node can only run the script as NT Authority.
So a part of my script looks like this:
Foreach ($i in $AllUsers) {
$ConID = (Get-ADUser -Identity $i -Properties ExtensionAttribute2 -Credential $svcPOSHCreds).ExtensionAttribute2
$ConDN = (get-aduser -filter {EmployeeID -eq $ManagerEID} -Credential $svcPOSHCreds).DistinguishedName
Set-ADUser -Identity $i -Manager $ConDN-Credential $svcPOSHCreds
Get-ADPrincipalGroupMembership $i -Credential $svcPOSHCreds | foreach {Remove-ADGroupMember $_ -Members $i - Confirm:$false -Credential $svcPOSHCreds}
Add-ADGroupMember -Identity 'Identity Con User' -Members $i -Credential $svcPOSHCreds
}
As you can see, I have to specify -Credential for every command I run because the user running this script doesn't have the rights to do some of the stuff.
So far I can count roughly 108 "-Credential" parameters to all of the commands between different APIs and AD...etc.
Is there a way that I can group the commands together so they utilize the same "Credentials" so I don't have to specify it each time when I run each command? I cannot specify how the script will be ran :( My limit is only to the inside of the PS1! so no "runas"...etc.
Is there a way that I can group the commands together so they utilize the same "Credentials" so I don't have to specify it each time when I run each command?
Yes, you can use the preference variable $PSDefaultParameterValues to automate this process.
Here is a simple example to demonstrate how it works, first we can define to test functions:
function Set-Something {
[CmdletBinding()]
param([pscredential] $Credential, [string] $SomeParam)
[pscustomobject]#{
SomeParam = $SomeParam
UserName = $Credential.UserName
Password = [Net.NetworkCredential]::new('', $Credential.Password).Password
}
}
function Get-Something {
[CmdletBinding()]
param([pscredential] $Credential, [string] $SomeParam)
[pscustomobject]#{
SomeParam = $SomeParam
UserName = $Credential.UserName
Password = [Net.NetworkCredential]::new('', $Credential.Password).Password
}
}
And for these 2 functions we will set their -Credential parameter as a Default Parameter:
$cred = [pscredential]::new('hello', (ConvertTo-SecureString 'world' -AsPlainText))
$PSDefaultParameterValues = #{
'Set-Something:Credential' = $cred
'Get-Something:Credential' = $cred
}
Then we can test if it works correctly, here we can assume that the $cred variable will be passed as default to both functions:
Get-Something -SomeParam 123
Set-Something -SomeParam 123
The result is:
SomeParam UserName Password
--------- -------- --------
123 hello world
123 hello world
You can also use wildcards here, so for example:
$PSDefaultParameterValues = #{
'*-Something:Credential' = $cred
}
Would target all functions with noun Something.
Related
Quick one? - As I'm still learning Powershell. I was wondering if it was possible to combine parameters inside a script with parameters entered on the command line?
i.e. I have a function like this as an example...
function GetInfo {
param ($SiteName, $Subnet, $Cred, $ComputerName)
Write-Host "Checking $Site and $ComputerName"
<# ... Additional logic to check computername prefix & subnet etc. #>
}
$SiteName = "London1"
$Subnet= "192.168.10.1/24"
$Cred = <supplied>
#$ComputerName = "blah1"
GetInfo $SiteName $Subnet $Cred $ComputerName
$SiteName = "London2"
$Subnet= "192.168.11.1/24"
$Cred = <supplied>
#$ComputerName = "blah2"
GetInfo $SiteName $Subnet $Cred $ComputerName
Now say inside the script I would specify the SiteName, Subnet, Cred.... but on the command line I would like to specify -ComputerName
But as I'm using the script & let's say I know that Lon1-PC1 is in "London1" I would like to do this on the calling command:
.\GetPCInf.ps1 -ComputerName "Lon1-PC1"
or
.\GetPCInf.ps1 -ComputerName "Lon2-PC1"
Obviously there will be additional logic inside the script to say that if the -ComputerName prefix is "Lon1" then do X or if -ComputerName prefix is "Lon2" then do Y..
Obviously I know I can just put the computername in the script, save it & run it.
So far when I try, nothing happens in relation to the -Computername return..
I haven't yet tried combining a parameter & Args - but I've read Args is not the best to use so I'm trying to avoid it.
If this can't be done then fair enough, just wondered if someone might know if this can be done, as it saves me typing in:
.\GetPCInf.ps1 -SiteName London1 -Subnet "192.168.10.1/24" -Cred mycreds -ComputerName "Lon1-PC1"
each time I want to run it....
I suppose I could create batch files to call the script & put %1 for computername in the batch file & call it that way, but just curious really..
Many thanks.
So far when I try, nothing happens in relation to the -Computername return..
Scripts are just functions stored in files - and they support param blocks and parameter declarations just like functions - so declare a $ComputerName parameter:
# GetPCInf.ps1
param(
[Parameter(Mandatory = $true)]
[string]$ComputerName
)
# define GetInfo function
function GetInfo {
param ($SiteName, $Subnet, $Cred, $ComputerName)
Write-Host "Checking $Site and $ComputerName"
<# ... Additional logic to check computername prefix & subnet etc. #>
}
# define table of arguments to pass to GetInfo
$GetInfoArgs = #{
ComputerName = $ComputerName
}
# add additional arguments based on computer name value
if($ComputerName -like 'Lon1-*'){
$GetInfoArgs += #{
SiteName = "London1"
Subnet= "192.168.10.1/24"
Cred = $(<# credential goes here #>)
}
} elseif($ComputerName -like 'Lon2-*') {
$GetInfoArgs += #{
SiteName = "London2"
Subnet= "192.168.11.1/24"
Cred = $(<# credential goes here #>)
}
} else {
$GetInfoArgs += #{
SiteName = "DefaultSite"
Subnet= "192.168.12.1/24"
Cred = $(<# credential goes here #>)
}
}
# invoke GetInfo function
GetInfo #GetInfoArgs
I want to create a PowerShell script which will disable the windows account, the target host name will be provided as an argument. Only admin should be able to execute this task.
This is what I have tried. Could someone please tell me if this approach is right or is there any better way to do this.
param( [Parameter(Mandatory=$true)] [String] $TargetHost ,
[Parameter(Mandatory=$true)] [String] $TargetUserName ,
[String] $User ,
[String] $Password)
# Set up a trap to properly exit on terminating exceptions
trap [Exception] {
write-error $("TRAPPED: " + $_)
exit 1
}
function DeactivateAccount($TargetHost , $TargetUserName ,$User , $Password){
$TargetHost = $TargetHost #Target Host on which windows account deactivation will be done.
$TargetUserName = $TargetUserName #User Name of Target.
$Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() #Domain name of the localhost.
$localHost = [System.Net.Dns]::GetHostName()
$localIP = [System.Net.Dns]::GetHostAddresses("$localHost")
#if TargetHost and LocalHost are same.
if($localHost -like $TargetHost -OR $localIP -like $TargetHost) {
if($Domain -eq [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()){
$process = net user $TargetUsername /domain /active:no #Performs the operation on the domain controller in the computer's primary domain.
} else {
$process = net user $TargetUsername /active:no
}
Write-host " $TargetUsername account deactivated "
}
#If TargetHost is remote Host.
else {
$User = $User #Creds to perform admin function.
$Password = $Password
$SecurePassword = new-Object System.Security.SecureString #Convert password into secure string.
$Password.ToCharArray() | % { $SecurePassword.AppendChar($_) }
$Cred = New-Object -typename System.Management.Automation.PSCredential -argumentlist "$User",$securePassword
$newSession = New-PSSession -ComputerName "$TargetHost" -credential $Cred #Used PSSession for persistent connection and credentials to Specify a user account that has permission to perform this action.
$export_username = Invoke-Command -Session $newSession -ScriptBlock {$username=args[1]} # Invoke-Command command uses the Session parameter(here newSession) to run the commands in same session.
if($Domain -eq [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()){
$process = Invoke-Command -Session $newSession -ScriptBlock {net user $username /domain /active:no}
} else {
$process = Invoke-Command -Session $newSession -ScriptBlock {net user $username /active:no}
}
Write-host " $TargetUsername account deactivated "
Remove-PSSession $newSession # Closes Windows PowerShell sessions.
}
if(-not $?) { # Returns true if last command was successful.
Write-Error "Windows Deactivation Failed!!"
exit 1
}
}
DeactivateAccount($TargetHost , $TargetUserName ,$User , $Password)
Couple of things:
Your meant to show some code to show you tried but since you're new to Powershell I'll let that slide :)
Is it a local windows account you are trying to disable or an AD one? For the purpose of this I'll assume local.
Grab this module: https://gallery.technet.microsoft.com/PowerShell-Module-to-255637a3
The dude basically made a module for exactly what you want to do :)
Note: If you have Powershell 5.1+ you won't need the module they added new cmdlets to do this natively.
Credential-wise I wouldn't worry, Powershell can't bypass windows security, it will execute with the permissions of the user that ran the script unless your script specifically gives credentials for another user in the commands.
Let me know how you get on.
Let's say I have 3 functions; A, B and C. Obviously this is super watered down but the logic is there.
A is a function that gathers a name.
function A {
Set-Variable -Scope Script -Name "First" -Value (Read-Host "First name?")
Set-Variable -Scope Script -Name "Last" -Value (Read-Host "Last name?")
}
Function B will use this name to create a O365 user with an msol session. No big deal.
function B {
Connect-MsolService -Credential $creds
"Add user foo"
$Name = "$Script:First $Script:Last"
"Success!"
}
While function C uses the name from A but references it within a PSSession to a remote AD server. This is where my knowledge totally breaks down. I've tried everything I know to reference these local variables within C.
function C {
New-ADUser
$Name = "$Script:First $Script:Last"
}
New-PSSession -computername AD -credential $ADcreds
Invoke-Command -ComputerName AD -ScriptBlock ${function:C} -credential $ADcreds
Remove-PSSession $s0
But they are totally wiped. I've tried to invoke a script block first to kind of define them again using the old data with no luck. My $Script:Name always comes up null in the PSSession.
Am I missing something huge?
The using: variable prefix should work here. If not, you should define a Param section within your function and pass the arguments using the -Argument parameter to the Invoke-Command cmdlet.
$scriptBlockC = {
New-ADUser
$Name = "$using:First $using:Last"
}
Invoke-Command -ComputerName AD -ScriptBlock $scriptBlockC -credential $ADcreds
Note that I removed the function keyword since you are just defining a scriptblock.
I am trying to add users in Active Directory. Those users need to have proxyAddresses. My problem is that those proxyAddresses are multiples and stored in an array.
I try :
$proxyAddresses = #("address1#test.com", "address2#test.com", "address3#test.com")
$userInstance = new-object Microsoft.ActiveDirectory.Management.ADUser
$userInstance.ProxyAddresses = $proxyAddresses
New-ADUser test -Instance $userInstance
And I get this error :
Invalid type 'System.Management.Automation.PSObject'. Parameter name: proxyAddresses
I would like to add this proxyAddresses array to the attribute proxyAddresses of my AD user but it don't seem to be possible.
Any idea how this could be done?
Anything wrong with using Set-ADUser?
$username = '...'
$proxyAddresses = 'address1#example.com', 'address2#example.com', 'address3#example.com'
New-ADUser -Name $username
Set-ADUser -Identity $username -Add #{
'proxyAddresses' = $proxyAddresses | % { "smtp:$_" }
}
I just had this same issue and I was pretty sure I was passing in a string array (that's how it was declared).
Problem was just before I sent my string array into AD I was passing it to "Sort-Object -Unique" - which unbeknownst to me was changing either the type or something that made the cmdlet unhappy.
Just FYI...Sort-Object can burn you in these circumstances.
So, in my testing of this. I made Get-ProxyAddresses at https://gist.github.com/PsychoData/dd475c27f7db5ce982cd6160c74ee1d0
function Get-ProxyAddresses
{
Param(
[Parameter(Mandatory=$true)]
[string[]]$username,
[string[]]$domains = 'domain.com'
)
#Strip off any leading # signs people may have provided. We'll add these later
$domains = $domains.Replace('#','')
$ProxyAddresses = New-Object System.Collections.ArrayList
foreach ($uname in $username) {
foreach ($domain in $domains ) {
if ($ProxyAddresses.Count -lt 1) {
$ProxyAddresses.Add( "SMTP:$uname#$domain" ) | Out-Null
} else {
$ProxyAddresses.Add( "smtp:$uname#$domain" ) | Out-Null
}
}
}
return $ProxyAddresses
}
It just returns as a collection. Pretty kludgy, but works for what I need. It also assumes the first username and first domain are the "primary"
I combined that with #ansgar's answer and tried just -OtherAttributes on New-Aduser
$proxyAddresses = Get-ProxyAddress -username 'john.smith', 'james.smith' -domains 'domain.com','domain.net'
New-ADUser -Name $username
-OtherAttributes #{
'proxyAddresses'= $proxyAddresses
}
Works perfectly and added the proxyAddresses for me right at creation, without having to have a separate set action afterwards.
If you are Going to do separate actions, I would recommend to use -Server, like below, so that you don't run into talking to two different DCs by accident (and you also know that the New-ADUser is finished and already there, you don't have to wait for replication)
#I like making it all in one command, above, but this should work fine too.
$ADServer = (Get-ADDomainController).name
New-ADUser -Server $ADServer -name $Username
Set-ADUSer -Server $ADServer -Identity $username -Add #{
'proxyAddresses' = $proxyAddresses | % { "smtp:$_" }
}
I'm writing a parameterized function and trying to use splatting to reuse the parameter-set.
$Computername='localhost'
$params = #{'computername'=$Computername;
'credential'='administrator'}
function Get-Diskinfo {
Param ($drive,$computername,$credential)
Get-WmiObject win32_logicaldisk -Filter "DeviceID = '$drive'" #params
}
On executing like this it fails doesn't recognize the user-inputed servername. It still takes the value of $Computername initially specified.
Get-Diskinfo -drive c: Server1 administrator
How can I get it to identify the user-inputed computername? Am I missing something ?
Instead of splatting random hashtable, you need to splat something that is defined automatically by PowerShell:
function Get-Diskinfo {
Param ($drive,$computername,$credential)
# Need to remove any parameter that is used "internally" and don't exist on cmdlet
$PSBoundParameters.Remove('drive') | Out-Null
Get-WmiObject win32_logicaldisk -Filter "DeviceID = '$drive'" #PSBoundParameters
}
Get-Diskinfo -drive c: Server1 administrator
Alternatively: use passed parameters to build $parms inside your function:
function Get-Diskinfo {
Param ($drive,$computername,$credential)
$parms = #{
computername = $Computername
credential = $credential
}
Get-WmiObject win32_logicaldisk -Filter "DeviceID = '$drive'" #parms
}
You aren't calling #parms until inside your function.
This should work, using the parameters within your function to pull the WMI query:
$Computername='localhost'
$parms = #{'computername'=$Computername;
'credential'='administrator'}
function Get-Diskinfo {
Param ($drive,$computername,$credential)
Get-WmiObject win32_logicaldisk -Filter "DeviceID = '$drive'" -ComputerName $computername -Credential $credential
}
And then use the splatting to run the function:
Get-Diskinfo -drive c: #parm
The function can still be run using the parameters directly:
Get-Diskinfo -drive c: Server1 administrator
Your $params hashtable gets it's value when you create it, not when it's used. Your $computername,$credential parameters are never referenced inside your function, which means that they are dead parameters.
You can't use splatting and normal function-calling with parameters for the same parameter at the same time. You should only use parameters inside your function scriptblock. Splatting is for the end-user only. #Tim Ferril has shown you how to write the function properly.
If you need to be able to use both default values AND be able to change ex. ComputerName for one cmdlet, then you should use default values and not splatting. Ex:
function Test-Global {
param(
$ComputerName = $global:PC,
$Username = $global:username
)
"Computername is '$ComputerName'"
"Username is '$UserName'"
}
#Set the reusable variables in global scope and call your function
$global:Username = "Frode"
$global:PC = "MyPC"
PS C:\Users\Frode> Test-Global
Computername is 'MyPC'
Username is 'Frode'
#If I need to modify the server for one function, I'd simply specify that parameter
PS C:\Users\Frode> Test-Global -ComputerName server1
Computername is 'server1'
Username is 'Frode'
To make it easier for user to set the default values, you could create a function for that too.
#Create your reusable parameters
function Set-Params {
param(
$ComputerName,
$Username
)
if($Username) { $global:Username = $Username }
if($ComputerName) { $global:PC = $ComputerName }
}
PS C:\Users\Frode> Set-Params -ComputerName Test1 -Username Test1
PS C:\Users\Frode> Test-Global
Computername is 'Test1'
Username is 'Test1'
PS C:\Users\Frode> Set-Params -ComputerName Test2
PS C:\Users\Frode> Test-Global
Computername is 'Test2'
Username is 'Test1'