PowerShell script to query remote Windows servers with no password prompt - windows

How do I write a PowerShell 4.0 script to return the virtualization vs. physical status of Windows 2003, 2008 and 2012 servers? I want to know if these servers are virtual. I want to run the script on a central Windows server. The script will interact with remote servers. I know of a PowerShell command to run on individual servers that will tell me. But I want it to check many remote servers at a time. Here is my basic (not-yet-working) script for one server, but it isn't working:
$ComputerName = "greatServer"
$UserName = "greatServer\jdoe"
$Password = Get-Content C:\Users\jdoe\Documents\password.txt
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $UserName , $Password
Get-WmiObject -Query "select * from win32_computersystem"
Here is the error that I get:
New-Object : Cannot find an overload for "PSCredential" and the argument count: "2".
At C:\Users\jdoe\Documents.pass.ps1:4 char:15 + $Credential = New-Object -TypeName System.Management.Automation.PSCredential -Ar ...+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo InvalidOperation: (:) [New-Object], MethodException + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand
The final query will have to be modified with a remove * and a comparison operation. The manufacturer in the results will indicate if it is a VMware, Hyper-V or physical server (e.g., Dell). I don't need help with modifying the final query. The select * will work assuming it can work on remote servers.
I really want help with the error that I am getting. I have written a working script, but it requires a user to enter the password manually. I just want help to make the script non-interactive (and suppress the prompt). I have been unsuccessful with passing a password argument to get around the interactive password entry requirement.
Does the file that holds that password need to be encrypted? Will this work on remote servers that are Windows Server 2003? I reviewed other scripts I found online. The "cat C:\password.txt" portion didn't seem to work for me. I tried the convertTo-string and securestring options. Both those caused errors too.

A better way is to store the credential securely. You will need to do this using the same user that will run the script (which means you might have to log on to that server as the serviceaccount which will run the script).
Anyway, from there you can do:
$cred = get-credential # Type in the greatserver credential
$cred | Export-Clixml -Path .\cred.xml
In your script you can now load the credential simply by running
$cred = Import-Clixml -Path .\cred.xml
This is the recommended way of persisting credentials to disk. Obviously, if you require a unique credential for each server you want to query this wouldn't work so well - you'd be better off using a domain account which is an administrator on all your servers (provided that your servers are actually joined to a domain).

Check out this script from the Technet Script repository, it pretty much does precisely what you need to do already.
The key to determining if a machine is virtual or not is to query the Win32_Bios and Win32_ComputerSystem classes to to check for Bios.Serial and ComputerSystem.Model and Manufacturer.
The way that the script works is to get the results of these queries then check to see if the Serial number field contains VMware, Xen, Microsoft or something else.
It's a lot of work to go creating a tool like this on your own since you'd need to research what to expect for various fields like bios, manufacturer and model and do some heavy lifting with scripts to get the right answer; I'd highly recommend you use this function instead.

Related

Regarding permissions for Get-ScheduledTask via remote powershell

My problem is as follows:
I have two users on a windows server (Windows Server 2016). One is my regular user (with admin privileges) and the other a technical user.
I would like to probe for a given scheduled task on a remote machine (which is a Windows Server 2016 as well). To do so i use "Invoke-Command" and pass the session as follows:
Invoke-Command -Session $session -ScriptBlock { Get-ScheduledTask -TaskName <task_name>}
For my regular user this works (i am using localhost for testing, but also verified this against a different server)
For the technical user it does not. However, the command
Get-ScheduledTask -TaskName <task_name>
executed in the technical users' powershell WORKS.
Further (executed as the technical user), the following works as well
Invoke-Command -Session $session -ScriptBlock { Get-ChildItem C:\ }
So what i take from this is that for the technical user in general remoting works as well access to the scheduled tasks. However, somehow the combination does NOT.
The exception is get is
Access denied
+ CategoryInfo : PermissionDenied: (MSFT_ScheduledTask:Root/Microsoft/...T_ScheduledTask) [Get-ScheduledTask], CimException
+ FullyQualifiedErrorId : HRESULT 0x80041003,Get-ScheduledTask
+ PSComputerName : <hostname>
So obviously this is a permission issue. But i do not get what i need to change to fix it. I tried to compare the different permission between my regular user and the technical user, however I did not spot anything immediately obvious.
Does anyone know what needs to be changed?
"Enable Account" and "Remote Enable" permissions need to be be granted via WMI Control on Remote\Microsoft\TaskScheduler namespace.

Run Powershell script that prompts for credentials before running command against remote device

I am trying to get a list of installed programs on another computer in our domain, which requires my domain admin credentials.
$Name = Read-Host -Prompt 'Enter Computer Name'
Start-Process powershell -Credential "company\adminusername"
Get-WmiObject -ComputerName $Name -Class Win32Reg_AddRemovePrograms | Select DisplayName, Version | Sort-Object Name
I don't know if this is correct or not. But it tells me my username and password is incorrect. Which is false. I must be doing something wrong here.
When I write scripts. I literally just want to double click on them, and let them fly. I'm trying to avoid running a script just to open another script as admin.
To hold your credentials:
$cred = Get-Credential
Then use the $cred variable when you need to.
Ended up finding a great solution here that not only allows me to get credentials but store them permanently for importing later for use in other scripts as well, with option to encrypt the credential file if needed:
https://www.jaapbrasser.com/quickly-and-securely-storing-your-credentials-powershell/

Scripting with alternate credentials and the difference between PSexec, Explorer shell, and Start-Process -Credential

I've run into an interesting problem which I can't quite figure out. I have a script which is intended to launch a certain application with alternate credentials. I can launch the application perfectly well using alternate creds using explorer and right clicking and choosing "Run As Different User" or with "PSexec - domain\user -p password "c:\app.exe"" but if I attempt to launch using:
$username = "username"
$password = "password"
$credentials = New-Object System.Management.Automation.PSCredential -ArgumentList #($username,(ConvertTo-SecureString -String $password -AsPlainText -Force))
Start-Process c:\app.exe -Credential ($credentials)
The application does in fact start using the alternate credentials but it then gets stuck in a loop/crashes. Using procmon it looks like it gets stuck on querying a directory that the app uses for caching, but I'm not sure it's relevant. I did try completely opening up the app folder ACL's (and all child objects recursively) to everyone but no luck. If I substitute other applications it works properly. I've used the "powershell" method to specify alt credentials with success many times before.
In any case, the script works fine using PSexec. I'd rather not have that dependency but it works for now. My question is, what is the difference from using the "powershell way" (using System.Management.Automation.PSCredential) to specify alternate credentials vs what PSexec or explorer is doing under the hood? I assume there must be something different going on and perhaps that information could help me trace why the application borks.
Thank you!
According to the technet pages of Start-Process and Psexec there is a difference in how the user profile is handled by default.
Start-Process does not load the profile unless you specify a switch:
-LoadUserProfile
Loads the Windows user profile stored in the HKEY_USERS registry key
for the current user. The default value is FALSE.
Psexec does always load the profile unless you specify a switch:
-e Does not load the specified account’s profile.
If the program expects some registry value for the cache location e.g. this could be a potential problem.

Run code block locally as a different user in powershell script

This is something incredibly simple, but I just can't get anything to work. I want to run a block code in a powershell script under a specific user. The keyword is locally and I'm using powershell 2.0.
Invoke-Command seems to require a remote host? I run the following and the error message that I see seems to suggest as much:
$strScriptUser = "DOMAIN\USER"
$strPass = "PASSWERD"
$PSS = ConvertTo-SecureString $strPass -AsPlainText -Force
$cred = new-object system.management.automation.PSCredential $strScriptUser,$PSS
Invoke-Command -ComputerName "." -scriptblock {
write-output "HI!"
} -Credential $cred
Start-Job with -ScriptBlock isn't supported with powershell 2.0? I run the following and the error message that I see seems to suggest as much:
$strScriptUser = "DOMAIN\USER"
$strPass = "PASSWERD"
$PSS = ConvertTo-SecureString $strPass -AsPlainText -Force
$cred = new-object system.management.automation.PSCredential $strScriptUser,$PSS
Start-Job -ScriptBlock {
write-output "HI!"
} -Credential $cred
Am I doing something wrong, or is there an alternative way?
Added: Here is what I'm trying to do in the first place. I'm making a scheduled task that runs when a user logs into/unlocks a terminal that writes logon information to a file. The scheduled task runs as the local user in order to get at the username, profile, etc. information. The logon information is then written to a log file using a different user account, which is the only account that can modify the file. To deter access to the logon credentials in the script I convert the script to an EXE using PS2EXE.
Here is another way.
# Get the other user's credentials
$credential = Get-Credential
# Execute a scriptblock as another user
$commands = #'
$env:username
# ... more commands ...
'#
Start-Process -FilePath Powershell -LoadUserProfile -Credential $credential -ArgumentList '-Command', $commands
# Execute a file as another user
$script = '.\path\name.ps1'
Start-Process -FilePath Powershell -LoadUserProfile -Credential $credential -ArgumentList '-File', $script
With the -LoadUserProfile switch, this has the added benefit of creating the user's profile if it does not already exist.
Another approach is impersonation, it is good option if you are not willing to enable remoting.
Check this and this out.
You should just put your code between
Push-ImpersonationContext $credential
and
Pop-ImpersonationContext
It would help to see the error messages you're not showing us, but I think the answer to your question is to use PowerShell Remoting as you tried with Invoke-Command. The computer name . is fine as is localhost but you do have to have remoting enabled on your machine to do it.
To enable remoting, run Enable-PSRemoting within powershell, or run winrm quickconfig in a regular command prompt.
If you already have remoting enabled, then you might be trying to do the remoting with a non-administrative user. If that's the case, take a look at the output of Get-PSSessionConfiguration. You'll get a list of endpoints and the permissions that are applied.
The endpoint you're connecting to by default is called Microsoft.Powershell and you could change the permissions with Set-PSSessionConfiguration (be sure to use the -ShowSecurityDescriptorUI parameter unless you want to mess with SDDL).
But instead of doing that, there should already be a group given access called BUILTIN\Remote Management Users which you can add your limited user to.
If none of this helps, give more details and error messages.
Edit
After seeing the explanation of what you're ultimately trying to accomplish, I have another suggestion for you.
Your existing scheduled task writes the information to a known location.
A different scheduled task running under the privileged user account picks up that information and puts it into the file that the limited user cannot access.
Bonus: start the second task from the first task.
This could be a quick compromise to do what you want without remoting and without exposing the credentials of the privileged user.
Issues with the current approach:
The major problem I have with your original idea is that you're going to need to embed the credentials into the script, so the limited user will have access to the credentials of the account that can modify the file anyway.
Ideally:
You would have a web service that you could invoke with your limited-user powershell script in which you can only give it the login information and not get anything back. So you'd hit a URL and do a POST or whatever with the data that you want to log, but that user can't ever retrieve any info. It might be a bit beyond what you're willing to do for this.

How do I get the current username in Windows PowerShell?

How do I get the current username in Windows PowerShell?
I found it:
$env:UserName
There is also:
$env:UserDomain
$env:ComputerName
On Windows, you can:
[System.Security.Principal.WindowsIdentity]::GetCurrent().Name
I thought it would be valuable to summarize and compare the given answers.
If you want to access the environment variable:
(easier/shorter/memorable option)
[Environment]::UserName -- #ThomasBratt
$env:username -- #Eoin
whoami -- #galaktor
If you want to access the Windows access token:
(more dependable option)
[System.Security.Principal.WindowsIdentity]::GetCurrent().Name -- #MarkSeemann
If you want the name of the logged in user
(rather than the name of the user running the PowerShell instance)
$(Get-WMIObject -class Win32_ComputerSystem | select username).username -- #TwonOfAn on this other forum
Comparison
#Kevin Panko's comment on #Mark Seemann's answer deals with choosing one of the categories over the other:
[The Windows access token approach] is the most secure answer, because $env:USERNAME can be altered by the user, but this will not be fooled by doing that.
In short, the environment variable option is more succinct, and the Windows access token option is more dependable.
I've had to use #Mark Seemann's Windows access token approach in a PowerShell script that I was running from a C# application with impersonation.
The C# application is run with my user account, and it runs the PowerShell script as a service account. Because of a limitation of the way I'm running the PowerShell script from C#, the PowerShell instance uses my user account's environment variables, even though it is run as the service account user.
In this setup, the environment variable options return my account name, and the Windows access token option returns the service account name (which is what I wanted), and the logged in user option returns my account name.
Testing
Also, if you want to compare the options yourself, here is a script you can use to run a script as another user. You need to use the Get-Credential cmdlet to get a credential object, and then run this script with the script to run as another user as argument 1, and the credential object as argument 2.
Usage:
$cred = Get-Credential UserTo.RunAs
Run-AsUser.ps1 "whoami; pause" $cred
Run-AsUser.ps1 "[System.Security.Principal.WindowsIdentity]::GetCurrent().Name; pause" $cred
Contents of Run-AsUser.ps1 script:
param(
[Parameter(Mandatory=$true)]
[string]$script,
[Parameter(Mandatory=$true)]
[System.Management.Automation.PsCredential]$cred
)
Start-Process -Credential $cred -FilePath 'powershell.exe' -ArgumentList 'noprofile','-Command',"$script"
(you may need a hyphen before noprofile, like so)
Start-Process -Credential $cred -FilePath 'powershell.exe' -ArgumentList '-noprofile','-Command',"$script"
$env:username is the easiest way
I'd like to throw in the whoami command, which basically is a nice alias for doing %USERDOMAIN%\%USERNAME% as proposed in other answers.
Write-Host "current user:"
Write-Host $(whoami)
[Environment]::UserName returns just the user name. E.g. bob
[System.Security.Principal.WindowsIdentity]::GetCurrent().Name returns the user name, prefixed by its domain where appropriate. E.g. SOMEWHERENICE\bob
Now that PowerShell Core (aka v6) has been released, and people may want to write cross-platform scripts, many of the answers here will not work on anything other than Windows.
[Environment]::UserName appears to be the best way of getting the current username on all platforms supported by PowerShell Core if you don't want to add platform detection and special casing to your code.
I have used $env:username in the past, but a colleague pointed out it's an environment variable and can be changed by the user and therefore, if you really want to get the current user's username, you shouldn't trust it.
I'd upvote Mark Seemann's answer:
[System.Security.Principal.WindowsIdentity]::GetCurrent().Name
But I'm not allowed to. With Mark's answer, if you need just the username, you may have to parse it out since on my system, it returns hostname\username and on domain joined machines with domain accounts it will return domain\username.
I would not use whoami.exe since it's not present on all versions of Windows, and it's a call out to another binary and may give some security teams fits.
Just building on the work of others here:
[String] ${stUserDomain},[String] ${stUserAccount} = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name.split("\")
$username=( ( Get-WMIObject -class Win32_ComputerSystem | Select-Object -ExpandProperty username ) -split '\\' )[1]
$username
The second username is for display only purposes only if you copy and paste it.
I didn't see any Add-Type based examples. Here is one using the GetUserName directly from advapi32.dll.
$sig = #'
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool GetUserName(System.Text.StringBuilder sb, ref Int32 length);
'#
Add-Type -MemberDefinition $sig -Namespace Advapi32 -Name Util
$size = 64
$str = New-Object System.Text.StringBuilder -ArgumentList $size
[Advapi32.util]::GetUserName($str, [ref]$size) |Out-Null
$str.ToString()
Sometimes the Username attribute has no data in Win32_ComputerSystem even though there's a user signed in. What works for me is to use quser and parse the output. It's not perfect, but it works. E.g.:
$quserdata = #()
$quserdata = quser
$userid = ($quserdata[1] -split ' ')[1]
$userid
Note: if this is run as the user who is logged in, quser adds '>' symbol to the output. Then you need to get rid of that symbol, but mostly this is needed for code run as system or another account than the one that is logged in.
If you're used to batch, you can call
$user=$(cmd.exe /c echo %username%)
This basically steals the output from what you would get if you had a batch file with just "echo %username%".
I find easiest to use: cd $home\Desktop\
will take you to current user desktop
In my case, I needed to retrieve the username to enable the script to change the path, ie. c:\users\%username%. I needed to start the script by changing the path to the users desktop. I was able to do this, with help from above and elsewhere, by using the get-location applet.
You may have another, or even better way to do it, but this worked for me:
$Path = Get-Location
Set-Location $Path\Desktop
In my case, I needed to retrieve the username to enable the script to change the path, ie. c:\users\%username%\. I needed to start the script by changing the path to the users desktop. I was able to do this, with help from above and elsewhere, by using the get-location applet.
You may have another, or even better way to do it, but this worked for me:
$Path = Get-Location
Set-Location $Path\Desktop

Resources