Ask for creds only if some specified - windows

I'm trying to create a module with a bunch of functions, but I'm stuck with a problem: sometimes I need to run functions with a different from current credentials. But the thing is: I don't want to ask for credentials if I didn't specify a username. Like this:
function MyFunction ($ComputerName='localhost', $UserName) {
if ($UserName) {
Get-WmiObject -Class Win32_OperatingSystem -ComputerName $ComputerName -Credential $UserName
} else {
Get-WmiObject -Class Win32_OperatingSystem -ComputerName $ComputerName
}
}
Can I somehow get rid of the if statement? Can I just let the function use current credentials unless I specify -UserName?
If I leave it like this:
Get-WmiObject -Class Win32_OperatingSystem -ComputerName $ComputerName -Credential $UserName
and call a function without specifying -UserName, it asks for credentialls every time. Yes, it uses current if I close the 'get-cred' box, but it's not what I want.

You could use splatting for dynamically building a parameter list, but you'd still need an if statement to decide whether or not to add the -Credential parameter:
function MyFunction ($ComputerName='localhost', $UserName) {
$params = #{
Class = 'Win32_OperatingSystem'
ComputerName = $ComputerName
}
if ($UserName) { $params['Credential'] = $UserName }
Get-WmiObject #params
}

You can populate a PSCredential object with current credentials using the default credential acquired like in here:
if ($UserName) {
$cred = get-credential $username # this prompts for credentials
} else {
$cred=[PSCredential][System.Net.CredentialCache]::DefaultCredentials
}
Then just use -credential $cred whenever you need without building a wall of if's.
EDIT: Apparently, as the current user's PSCredential object has its password as SecureString encrypted by the very same user's private key, if one would be able to get a PSCredential out of default credentials object, he'll be able to decrypt the password into plaintext, so this hole existed but was eventually closed. (Maybe let this answer hang here so that people won't get the asme error as I did) This question has the canonical answer on what arised in here.
Another way to do this might be using a variation of splatting, explained in detail by Ansgar Wiechers and here to construct only credential block in a single if statement and then use that block wherever you need, instead of writing direct -credential parameter.
$creds=#{}
if ($UserName) {
$cred = get-credential $username # this prompts for credentials
$creds['Credential']=$cred
}
And then add #creds wherever you require alternate credentials:
Get-WmiObject -Class Win32_OperatingSystem -ComputerName $ComputerName #creds
This way you'll be asked for user's password once if $UserName is supplied, and then the $creds will either be empty or contain valid credentials of $UserName, so all subsequent if's can be replaced by explicit adding of #creds.

Your never really going to lose the IF, but you could create a string on the fly and then invoke it as and expression:
If $UserName is $null the credential parameter will not be added to the expression.
function MyFunction ([String]$ComputerName='localhost', [String]$UserName) {
Invoke-Expression ("Get-WmiObject -Class Win32_OperatingSystem -ComputerName $ComputerName $(IF($UserName){'-Credential $UserName'})")
}

Related

Using PowerShell to Check for Newly Created Local Admins

So, as the title says: I am trying to write up a PowerShell script that checks to see if any user accounts have been added to the Local Administrators group or have been added as an Administrator on the local machine. I have been using Get-EventLog and Get-WinEvent in an attempt to accomplish what I a trying to do. The problem I am having is isolating or extracting the information I want out of the event logs.
This is what I have so far:
$Date = ((Get-Date).AddDays(-1)).Date
$Events = Get-WinEvent -FilterHashtable #{
StartTime = $Date
LogName = 'Security'
ProviderName = 'Microsoft-Windows-Security-Auditing'
ID = 4732
}
I figure, if I can get the Username of the account that was added; which group or permissions it was given; and the date it was created, I can selectively output that information for each log over the last 24 hours. I'm not sure if I should be trying to use Get-Item or Get-Content, or if there is another way I should be trying to tackle this.
Unless you have powershell v6.0+ installed, you'll need to use -FilterXPath instead:
$Alerts = Get-WinEvent -LogName Security -FilterXPath #'
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">*[System[
EventID=4732 and
TimeCreated[timediff(#SystemTime) <= 604800000]]] and
*[EventData[Data[#Name="TargetDomainName"]='BUILTIN']] and
*[EventData[Data[#Name='TargetUserName']='Administrators']]
</Select>
</Query>
</QueryList>
'#
You can use -FilterHashTable if you are running powershell v6.0 or higher. I would use something like this to check for those events. My servers don't all have v6, so I would have to run it remotely like so:
#Requires -Version 6.0
$alerts = Get-WinEvent -ComputerName $computername -FilterHashtable #{
StartTime = ((Get-Date).AddDays(-1)).Date
LogName = 'Security'
ProviderName = 'Microsoft-Windows-Security-Auditing'
ID = 4732
TargetDomainName='Builtin' ## These fields require
TargetUserName='Administrators' ## Powershell v6.0+
}
I'll include how I convert event log's local SIDs to usernames for reports, since it's a pain
# Process event(s)
if ($alerts) { foreach ($event in $alerts) {
# Try and convert SID to account name:
$sid = $event.Properties[1].Value.Value
$localuser = ([System.Security.Principal.SecurityIdentifier]::new($sid)
).Translate([System.Security.Principal.NTAccount])).Value
}
# Use SID in case of failure
if (!$localuser){$localuser=$event.Properties[1].Value}
# Example action
Write-Warning "User $localuser added to local Administrators group on $computername by user $($event.Properties[7].Value)\$($event.Properties[6].Value)"
}}
# Outputs
WARNING: User Server01\local-user01 added to local Administrators group on Server01 by user DOMAIN\AdminUser01

powershell auto login script not working

This is my url
https://webmail.fasttrackteam.com/Login.aspx
I am able to open this path in IE.
but unable to set loginid & password.
Also dont know how to fire click event.
following is code that I have tried.
$ie = New-Object -com InternetExplorer.Application
$ie.Navigate("https://webmail.fasttrackteam.com/Login.aspx")
$ie.visible = $true
$doc = $ie.document
$user = $doc.getElementById("ctl00_MPH_txtUserName")
$password = $doc.getElementById("ctl00_MPH_txtPassword")
$submit = $doc.getElementById("ctl00_MPH_btnEnterClick")
$user.value = "emailid"
$password.value = "password"
$submit.Click();
$ie.Quit();
$ie.Document.body | Out-File -FilePath C:\Users\amol.kshirsagar\Documents\FastTrack\Work\Extra\AutoLogin\log.txt
EDIT
This is error that I am getting,
You cannot call a method on a null-valued expression.
You call the Quit() method before accessing the Document.body member. As the Quit() call, well, quits the application, don't you think it should be somewhat peculiar to access its data afterwards?
Try accessing the member first, then quitting the browser instance.

PowerShell : GetNewClosure() and Cmdlets with validation

I'm trying to understand how .GetNewClosure() works within the context of a script cmdlet in PowerShell 2.
In essence I have a function that returns an object like so:
function Get-AnObject {
param(
[CmdletBinding()]
[Parameter(....)]
[String[]]$Id
..
[ValidateSet('Option1','Option2')]
[String[]]$Options
)
...
$T = New-Object PSCustomObject -Property #{ ..... }
$T | Add-Member -MemberType ScriptProperty -Name ExpensiveScriptProperty -Value {
$this | Get-ExpensiveStuff
}.GetNewClosure()
..
}
Providing I do not have the validate set options the closure appears to work fine. If it is included however the new closure fails with the following error.
Exception calling "GetNewClosure" with "0" argument(s): "Attribute cannot be added because it would cause the variable Options with value to become invalid."
Presumably the closure is trying to capture the context of the call to the Cmdlet. Since the parameter "Options" is not bound at all this is not nicely with the parameter validation.
I imagine it's possible to avoid this by placing validation as code within the body of the Cmdlet instead of making use of the [Validate*()] decorators -- but this seems nasty and quite obscure. Is there a way of fusing these two ideas?
The "Attribute cannot be added" message is (or was) a PowerShell bug, I've submitted it to Microsoft with this bug report. That particular issue seems to have been fixed, (perhaps around V5.1. but anyone interested in Powershell Closures may still find info below interesting.
There is a workaround which works in earlier versions, but first here's a simplified repro case that produces the same error:
function Test-ClosureWithValidation {
[CmdletBinding()]
param(
[Parameter()]
[ValidateSet('Option1','Option2')]
[String[]]$Options
)
[scriptblock] $closure = {"OK"}.GetNewClosure();
$closure.Invoke()
}
Test-ClosureWithValidation -Options Option1
The workaround depends on the fact that GetNewClosure() works by iterating over the local variables in the calling script's context, binding these local variables into the script's context. The bug occurs because its copying the $Options variable including the validation attribute. You can work around the bug by creating a new context with only the local variables you need. In the simple repro above, it is a one-line workaround:
[scriptblock] $closure = &{ {"OK"}.GetNewClosure();}
The line above now creates a scope with no local variables. That may be too simple for your case; If you need some values from the outer scope, you can just copy them into local variables in the new scope, e.g:
[scriptblock] $closure = &{
$options = $options;
{"OK $options"}.GetNewClosure();
}
Note that the second line above creates a new $options variable, assigning it the value of the outer variable, the attributes don't propagate.
Finally, I'm not sure in your example why you need to call GetNewClosure at all. The variable $this isn't a normal local variable, it will be available in your script property whether or not you create a closure. Example:
function Test-ScriptPropertyWithoutClosure {
[CmdletBinding()]
param(
[Parameter()]
[ValidateSet('Option1','Option2')]
[String[]]$Options
)
[pscustomobject]#{ Timestamp= Get-Date} |
Add-Member ScriptProperty ExpensiveScriptProperty {
$this | get-member -MemberType Properties| % Name
} -PassThru
}
Test-ScriptPropertyWithoutClosure -Options Option1 | fl
I believe this might work:
function Get-AnObject {
param(
[CmdletBinding()]
[Parameter(....)]
[String[]]$Id
..
[ValidateSet('Option1','Option2')]
[String[]]$Options
)
...
$sb = [scriptblock]::create('$this | Get-ExpensiveStuff')
$T = New-Object PSCustomObject -Property #{ ..... }
$T | Add-Member -MemberType ScriptProperty -Name ExpensiveScriptProperty -Value $sb
.. }
That delays creation of the script block until run time.

Query AD to find all the Computer in an OU with TC in their Name

I'm trying to search AD for all machines in a given OU that have 'TC' in their name, this is what I have so far, but its returning all machines, I need it to return just the machines with 'TC' in their name.
$root = ([adsi]'LDAP://OU=PCs,OU=Student Computers,DC=student,DC=belfastmet,DC=int','objectCategory=computer')
$searcher = new-object System.DirectoryServices.DirectorySearcher($root)
$searcher.filter = "(objectCategory=computer)"
$searcher.pageSize=1000
$searcher.propertiesToLoad.Add("name")
$computers = $searcher.findall()
$computers | foreach {$_.properties.name}
Not really sure what I should be doing from this point, I am a Powershell Newbie.
You have two options. You can get all computers and then filter using Powershell cmdlets, or your ldap filter reflects what you want (better). Try this:
$searcher.filter = "(&(objectCategory=computer)(cn=TN*))"
With ActiveDirectoryModule, you could find machines in a specific OU using filter and limit the search to the OU (assuming YourDomain.com\YourOU in the example below) you want with SearchBase:
$adcomputers = Get-ADComputer -Filter {name -like "TC*"} -Searchbase "OU=YourOU,DC=YourDomain,DC=com"
If you have the AD module available to you, you can do this with a single cmdlet.
get-adcomputer -filter {name -like "*TC*"}

PowerShell folder permission error - Some or all identity references could not be translated

I am running this script as Admin and It does create the folders requred, just does not set the appropriate permissions.
$Users = Get-Content "D:\New_Users.txt"
ForEach ($user in $users)
{
$newPath = Join-Path "F:\Users" -childpath $user
New-Item $newPath -type directory
$UserObj = New-Object System.Security.Principal.NTAccount("DOMAIN",$user)
$acl = Get-Acl $newpath
$acl.SetAccessRuleProtection($True, $False)
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("O1OAK\$user","AppendData,CreateDirectories,CreateFiles,DeleteSubdirectoriesAndFiles,ExecuteFile,ListDirectory,Modify,Read,ReadAndExecute,ReadAttributes,ReadData,ReadExtendedAttributes,ReadPermissions,Synchronize,Traverse,Write,WriteAttributes,WriteData,WriteExtendedAttributes","ContainerInherit, ObjectInherit","None","Allow")
$acl.SetAccessRule($accessRule)
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("NT AUTHORITY\SYSTEM","FullControl","ContainerInherit, ObjectInherit","None","Allow")
$acl.SetAccessRule($accessRule)
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("BUILTIN\Administrators","FullControl","ContainerInherit, ObjectInherit","None","Allow")
$acl.SetAccessRule($accessRule)
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("1OAK\$user","Delete","ContainerInherit, ObjectInherit","None","Allow")
$acl.removeAccessRule($accessRule)
$acl.SetOwner($UserObj)
$acl | Set-Acl $newpath
}
The first error in a string of 3 that I get is below. I think it is the most important and will fix the other 2.
Exception calling "SetAccessRule" with "1" argument(s): "Some or all identity references could not be translated."
At D:\DOMAIN\IT\IT Private\User Drives\user_folders.ps1:12 char:20
+ $acl.SetAccessRule <<<< ($accessRule)
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
The error is pretty self explanatory: Some or all identity references could not be translated.
This means the account couldn't be found. So what you have to do is verify your accounts. Since you're adding 4 ACE's, you'll need to identify which is invalid.
The easiest way to do this is to debug through, line by line using the ISE or PowerGUI.
I tried your code with "NT AUTHORITY\SYSTEM" and "BUILTIN\Administrators" and it works so the issue is with "O1OAK\$user" or "1OAK\$user". You likely have an invalid account in your text file.
a gotch with the user ID is that AD truncates the username, so a user with a long name "j_reallylongname" will have a samid (Security Account Manager (SAM) account name) which is truncated. (j_reallylong)
so when fetching usernames, make sure you verify against the AD before using it.
When i've got the upns, so i run a dsget query to get the samid then use that to build the identity reference.
Adding this in case any C#/ASP.NET developers get this (which is my scenario, and I found this post).
I am using .NET Core in a corporate environment, and I need to check UserGroups as part of security. The code is like (where "user" is a ClaimsPrincipal):
var windowsIdentity = user.Identity as WindowsIdentity;
if( windowsIdentity is null )
throw new Exception( $"Invalid Windows Identity {user.Identity.Name}" );
return windowsIdentity.Groups
.Select( g => g.Translate( typeof( NTAccount ) ).Value );
Anyway, someone in charge of groups deleted a group I was part of, and the AD replication lag caused me to get the error in the title. A logoff and/or reboot worked just fine.
For me it was a case of where i verified whether the script execution knew the password by using $user = Get-Credential "username". i had to turn my $user into $user.UserName To give the script parameters the value they were expecting

Resources