Powershell - Compiling with Modules - powershell

I have created a log-on script based on Active-Directory Module, that queries the user group membership in order to map his drives etc.
I have compiled it with PowerGui, and created an EXE file.
the problem is, the module doesn't exist on the users computers.
Is there a way to do this without the module, or add the module to the compilation?

For group memberships, you could also get it without connecting to AD, and parse the output of the WHOAMI utility
$groups = WHOAMI /GROUPS /FO CSV | ConvertFrom-Csv | Select-Object -ExpandProperty 'Group Name'
if($groups -contains 'group1')
{
do something
}

One way is using Active-Directory Service Interface (ADSI).
you can find in another SO post (Can I match a user to a group accross different domains?) one way to find all the groups a user belongs to, using ADSI, in the post it's a C# code, but it's easy to translate.
Here is a small example of a simple search to begin.
Clear-Host
$dn = New-Object System.DirectoryServices.DirectoryEntry ("LDAP://WM2008R2ENT:389/dc=dom,dc=fr","jpb#dom.fr","Pwd")
# Look for a user
$user2Find = "user1"
$Rech = new-object System.DirectoryServices.DirectorySearcher($dn)
$rc = $Rech.filter = "((sAMAccountName=$user2Find))"
$rc = $Rech.SearchScope = "subtree"
$rc = $Rech.PropertiesToLoad.Add("mail");
$theUser = $Rech.FindOne()
if ($theUser -ne $null)
{
Write-Host $theUser.Properties["mail"]
}
Another way is to use System.DirectoryServices.AccountManagement Namespace.
This way is also using ADSI, but it's encapsulated, and you need the Framework .NET 3.5. You will also find in the same post but in the Edited (2011-10-18 13:25) part, a C# code using this way.
You can also use WMI :
$user2Find = "user1"
$query = "SELECT * FROM ds_user where ds_sAMAccountName='$user2find'"
$user = Get-WmiObject -Query $query -Namespace "root\Directory\LDAP"
$user.DS_mail
You can use this solution localy on your server or from a computer inside the domain, but it's a bit more complicated to authenticate to WMI from outside the domain.

Related

How to query Active Directory for a domain user via powershell without installing RSAT tools on client computer

I've search around on the internet for a way to query Active Directory for a user via powershell (or cmd) without installing RSAT tools but nothing has worked. My script needs to query AD to confirm that the user exists before moving on to the next step. The computer is on the domain and Ideally, i don't want my script to download and install RSAT tools on the client workstation.
I was able to get the following code to work. However, I have to be logged in as a domain user. Ideally, I would like this to work from a local admin user on a workstation that is bound to AD.
$search = [adsisearcher]"(&(ObjectCategory=Person)(ObjectClass=*)(cn=*))"
$users = $search.FindAll()
foreach($user in $users)
{
$CN = $user.Properties['CN']
$DisplayName = $user.Properties['DisplayName']
$SamAccountName = $user.Properties['SamAccountName']
"CN is $CN"
"Display Name is $DisplayName"
"SamAccountName is $SamAccountName"
}
You can you the [ADSI] type accelerator or you can .Net class like below. Replace druffin with the username in AD. For an example of a username run $env:USERNAME
$adsiNET = New-Object system.directoryservices.directorysearcher "name=druffin"
$adsiNET.FindOne()

Using Powershell to find last step run from an SQL agent job

I have a powershell script that provides SQL Agent Job information. The script is meant to monitor the jobs and tell me if they failed the last time they ran. However i also want to know which was the last run step. and i can't seem to figure out how.
I am querying the Sql server mangement object, because it needs to be usable on multiple remote servers (remote connections) and i wish to avoid running SQL scripts.
Keep in mind i'm rather new to powershell.
This is the code i have so far: And i have loaded the SMO libraries, i just didn't show it the copied script.
## Get Jobstep Class SMO
Push-Location; Import-Module SQLPS -DisableNameChecking; Pop-Location;
$JobStep = New-Object microsoft.sqlserver.management.smo.agent.jobstep
## Run the select from the local SQL server management objects
$SQLSvr = "."
$MySQLObject = new-object Microsoft.SqlServer.Management.Smo.Server `
$SQLSvr;;
$Select = ($MySQLObject.JobServer.jobs) `
| Select Name, isEnabled, `
lastRunDate, lastRunOutCome, NextRunDate `
| Where-Object {$_.LastRunDate -ge ((Get-Date).adddays(-2)) } `
| ft -AutoSize;
$Select
The push-location section is meant to get the correct smo class for selecting the job step (unsure if it is right), however i haven't added anything from it.
Now the script does work, i get no errors and i get returned the overall information i want, but i cannot figure out how to add the jobstep - and i have consulted google. I'm well aware that i need to add more to the select, but what is the issue for me. So, how do i extract the last run job step from SQL Agent job, using the SMO, and add it to the above script?
You can use the SqlServer module from the PowerShell Gallery (Install-Module SqlServer), and then something like:
$h = Get-SqlAgentJobHistory -ServerInstance servername -JobName jobname
$h[0] will give you the last step ran.
This will give you the result in the format you wanted:
Get-SqlAgentJob -ServerInstance servername |
Where-Object {$_.LastRunDate -ge ((Get-Date).AddDays(-2))} | ForEach-Object {
$h = Get-SqlAgentJobHistory -ServerInstance servername -JobName $_.Name
[PSCustomObject]#{
Name = $_.Name
IsEnabled = $_.IsEnabled
LastRunDate = $_.LastRunDate
LastRunOutcome = $_.LastRunOutcome
NextRunDate = $_.NextRunDate
LastRunStep = $h[0].StepName
}
}

Powershell run script with Import-Module

I have created a module for my admin group with some functions that automate some procedures we commonly perform (add administrators to remote machines, C drive cleanup, etc...)
One of the prerequisites for these functions is the generation of a series of 7 credentials, one for each domain we work in.
Is there a way to get a scriptblock to run when you import a module, or is this something I should add to each persons profile?
A commenter mentioned I could just add it to the module.psm1 file, but that didn't work. Here is the code I am trying to run.
$creds = Import-Csv [csvfile]
$key = Get-Content [keyfile]
foreach ($cred in $creds) {
$user = $cred.User
$password = $cred.Hash | ConvertTo-SecureString -Key $key
$i = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user,$password
New-Variable -Name ($cred.Domain + "_Cred") -Value $i -Force
}
Running this manually works fine, but it isn't creating the credentials when run from the Import-Module command.
Any code that's not a function will run when you import the module.
A handy tip when working with modules: & and . have what may be undocumented functionality. With either you can give two arguments, the first is a module reference (from get-module or similar) and second is a script. With the module reference parameter the script will run in the context of the module. So for example:
& $myMod {$usa_cred}
will output the value of $use_cred even if it hasn't been exported. This is useful for debugging scripts. Also modules can have embedded modules and & $myMod {gmo} will list those sub modules. By nesting & or . you can access sub-modules context.

How to create a com object in another domain?

I'm trying to use PowerShell to quickly find the Scheduled Tasks in the root folder of a remote server. I find all sorts of scripts that others have written, but they're either looking at the localhost or on a server in the same domain. I support servers in dozens of domains, so I need some way to pass along credentials.
Here's the meat of my script:
$server = "<computername>"
$schedule = new-object -com("Schedule.Service")
$Schedule.connect($server)
$folder = $schedule.GetFolder("")
$tasks = $folder.GetTasks("")
foreach($task in $tasks) {
if (($task = $Folder.GetTasks(0))) {
$Tasks| ForEach-Object {[array]$results += $_}
$Tasks | Foreach-Object {
New-Object -TypeName PSCustomObject -Property #{
'Name' = $_.name
<etc.>
<etc.>
}
}
}
That code works fine either on my localhost or a server in the same domain as my workstation. In other scripts, I use Get-Credential to create $creds and (in various ways) pass that to the appropriate cmdlet. But with this one, I'm not sure. 'New-Object' doesn't accept a -Credential parameter. I've tried wrapping various parts inside an Invoke-Command scriptblock, since that accepts -Credential, but it fails in various ways. I'm not sure what needs to be wrapped in Invoke-Command--just the new-object? The foreach loop? The entire thing?
Thanks in advance.
When doing the Connect call, you can pass the server, domain, username, and password:
$Schedule.Connect($serverName, $user, $domain, $password);
This should allow you to use that object on the new domain.
MSDN Reference

OpenRemoteBaseKey() credentials

I'm attempting to use powershell to access a remote registry like so:
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine", $server)
$key = $reg.OpenSubkey($subkeyPath)
Depending on some factors that I'm not yet able to determine I either get
Exception calling "OpenSubKey" with "1" argument(s): "Requested registry access is not allowed."
Or
System.UnauthorizedAccessException: Attempted to perform an unauthorized operation.
at Microsoft.Win32.RegistryKey.Win32ErrorStatic(Int32 errorCode, String str)
at Microsoft.Win32.RegistryKey.OpenRemoteBaseKey(RegistryHive hKey, String machineName)
It seems pretty clear that this is because the user I'm running the powershell script as doesn't have the appropriate credentials to access the remote registry. I'd like to be able to supply a set of credentials to use for the remote registry access, but I can find no documentation anywhere of a way to do this. I'm also not clear on exactly where to specify which users are allowed to access the registry remotely.
Just thought I'd add my answer to anyone with this problem as well. It seems there is no way to add Credentials using RemoteRegistry. You can however use WMI to query a remote registry using alternative credentials as follows:
$reg = Get-WmiObject -List -Namespace root\default -ComputerName RemotePC -Credential "Domain\User" | Where-Object {$_.Name -eq "StdRegProv"}
From here you can call standard Registry methods. The below example will return the operating system.
$HKLM = 2147483650
$reg.GetStringValue($HKLM,"SOFTWARE\Microsoft\Windows NT\CurrentVersion","ProductName").sValue
Hope this helps someone :)
Are you running remote registry service? It is disabled by default and that must be causing the issue. Check the status of this service on all remote machines you are trying to access.
I couldn't comment directly on bentaylr's entry above, but I've taken what he contributed and added PSCredentials creation (figured out from here) to allow you to hard code credentials into the script.
Peace of mind disclaimer: Be careful when using plaintext credentials in a script. In my case, I'm using generic credentials on machines I'm launching. Depending on your case, you might consider creating an encrypted credential file to store the password in (see link above).
The credentials you use would need to be able to access the registry if you were logged into that user on the machine you are targeting.
$user = "Domain\Username"
$pass = ConvertTo-SecureString "Password" -AsPlainText -Force
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user,$pass
$reg = Get-WmiObject -List -Namespace root\default -ComputerName $server -Credential $cred | Where-Object {$_.Name -eq "StdRegProv"}
$HKLM = 2147483650
$value = $reg.GetStringValue($HKLM,"Software\Microsoft\.NetFramework","InstallRoot").sValue
$key.OpenSubKey($subkeyName) opens the subkey in write protected mode,
$key.OpenSubKey($subkeyName,$true) opens it in writable mode
Therefore after $key.OpenSubKey($subkeyName,$true) you should be able to create a new subkey or value
If you try the same thing after $key.OpenSubKey($subkeyName) you will get "UnauthorizedAccessException"
PS C:\>$regKey.OpenSubKey
OverloadDefinitions
Microsoft.Win32.RegistryKey OpenSubKey(string name, **bool Writable**)
try
PS C:\>$key.OpenSubKey($subkeyName,**$true**)
http://msdn.microsoft.com/en-us/library/xthy8s8d%28v=vs.110%29.aspx
Came looking for the answer to your question, but in a little googling this morning I noticed that the first parameter is a type rather than a String... hope this helps:
$machine = "<Machine Name Goes Here>"
$type = [Microsoft.Win32.RegistryHive]::LocalMachine
$regkey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($type,$machine)
$subkey = $regKey.OpenSubKey($key)
foreach ($sub in $regKey.GetSubKeyNames()){$sub}
I wanted to first thank all for answers above really helpful, wanted to add that you can use Get-Credential command to collect credentials without having to hard code it in your script. I have written using the above suggestions into my script the following code and query:
$userCredentials = Get-Credential -Credential <domain\username>
$objReg = Get-WmiObject -List -Namespace root\default -ComputerName $server -Credential $userCredentials | Where-Object{$_.Name -eq "StdRegProv"}
$subKeyNames = $objReg.EnumKey($HKLM,"SOFTWARE\Microsoft\Updates\Microsoft .Net Framework 4.5.1").sNames
The above code returns all sub key names in the specified key so that I can determine installed updates other than OS which have been applied to a server. If you want to determine all collection possibilities with the $objReg variable then run:
$objReg | Get-Member
You will see a list of all possible queries which can be performed against the registry. Hope this helps!