I am working on installing software across multiple servers with various versions of Powershell.
Originally I was using New-PSDrive cmdlets to mount a drive and copy/install files from. Some servers now complain that New-PSDrive does not accept -Credential argument despite having PS4.0.
I am now trying to use .MapNetworkDrive for legacy purposes but cannot seem to be able to pass Password to it via a variable:
$creds = Get-Credential
$net = New-Object -ComObject WScript.Network
$net.MapNetworkDrive("u:", "\\myshare", $false, "$creds.UserName","$creds.Password")
I get an error: Exception calling "MapNetworkDrive" with "5" argument(s): "The specified network password is not correct.
Any suggestions?
Many thanks,
you have two problems going on here. [grin]
WScript COM objects do not understand SecureString objects
your $Creds.Password is stored as a System.Security.SecureString and that is not something that the WScript stuff knows about.
you can see that with $Creds.Password.
you would need to use something like >>> $Creds.GetNetworkCredential().Password.
compound objects - like "$creds.UserName" - don't expand as expected when inside double quotes
you will get System.Management.Automation.PSCredential.UserName from that.
you need to force the variable-dot-property to evaluate before the string is expanded. thus >>> "$($Creds.UserName)".
so your line of code ...
$net.MapNetworkDrive("u:", "\\myshare", $false, "$creds.UserName","$creds.Password")
... would become something like this ...
$net.MapNetworkDrive(
"u:",
"\\myshare",
$false,
"$($creds.UserName)",
"$($Creds.GetNetworkCredential().Password)"
)
however, i suspect you could leave off the quotes for the last two lines ... and then would not need the $() wrapper. [grin]
Related
I have currently had to take a huge leap from my unix scripting to the MS side of things and found myself overwhelmed with PowerShell.
My situation is as follows:
I have a script script.ps1 which can be only run under specific windows account. In order to facilitate the use, it was decided that if user runs the script from a different account, it will pop up a query for credentials and restart itself from within (similarly to recursion), but importantly - maintaining the input parameters.
I have found out, that the Invoke-Command is probably what I am looking for, but I cannot seem to be able to build the PS query for this.
my code snippet looks like
if(!([System.Environment]::UserName -eq $user)){
$Credential = Get-Credential -credential INTRANET\$user
Invoke-Command -FilePath $script -Credential $Credential -ArgumentList $arguments
}
where $user contains the desired user, $script contains filepath to the script.ps1 and $arguments contain command line arguments that were passed to the script as a String, i. e. -order 66 -location UAT
but currently I get an error
Parameter set cannot be resolved using the specified named parameters.
...
FullyQualifiedErrorId : AmbiguousParameterSet
I tried shuffling the parameters around, I tried using Start-Process instead of Invoke-Command, but everything resulted in same or similar errors.
Also, because I am really new to the powershell, please do not hesitate to offer different solution, if it is viable. I do not know the capabilities of the language well.
Lastly, please note that the starting point is always powershell prompt running with non-elevated user account. Unfortunately, the option to start up powershell under a different account in the first place is not available to us.
The problem probably is that you specify the parameters stored in the variable $arguments as string in the regular format like you said: -order 66 -location UAT
The parameter -ArgumentList works differently, its an array used for array splatting. So you can't pass the values by the parameter name. You have to pass the values by parameter order, e.g.:
$Arguments = #(66,'uat')
Invoke-Command -FilePath $script -Credential $Credential -ArgumentList $Arguments
See Parameter Argumentlist.
See Array Splatting.
The value 66 is passed to the first parameter, the value uat to the 2nd... So you must know the order of the parameters and insert the related values into the array at the right position.
To control the position of the parameters, the param specification in the other script should at least have:
param (
[parameter(Position=1)]
[int]$order,
[parameter(Position=2)]
[string]$location
)
I have a powershell script in my release pipeline stage that runs a command and passes values of a secret variable to it. The issue is that the Logs show each and every command as they are run including each arguments passed, one of which is from a secret variable.
How do I make the powershell output not show the command it is running? output of the command is okay to show if it can't be hidden.
Secrets shouldn't be converted to plain text but kept as such and passed as a SecureString to your application. In other words, the solution lays in making sure that your concerned application accepts a hashed password, a SecureString or a PSCredential object also knowing that sending a plain text password to an application isn't secure by itself.
#iRon Say this to Microsoft. I am trying to call their schtasks
I just did: #16502: Set-ScheduledTask shouldn't accept a plain text Password
As a workaround, you might keep your password covered in a SecureString as long as possible:
$Credentials = Get-Credential
Set-ScheduledTask -User $Credential.UserName -Password $Credential.GetNetworkCredential().Password
This will prevent that the passwords are revealed by logging but as there is still a potentially security risk that the password could be read from memory, I recommend to do a garbage collection ([system.gc]::Collect()) right after this command.
⚠️ Important
A SecureString object should never be constructed from a String, because the sensitive data is already subject to the memory persistence consequences of the immutable String class. The best way to construct a SecureString object is from a character-at-a-time unmanaged source, such as the Console.ReadKey method.
To be completely safe, you might also consider to run Set-ScheduledTask (without -User and -Password) under the credentials of the targeted user Start-Process -Credential $Credential ...
Update 2022-02-24:
Sadly😭, I got zero response on my feedback hub "Set-ScheduledTask shouldn't accept a plain text Password" (security) issue. Therefore, I have also just created a new Microsoft Feedback Portal issue for this: Windows-PowerShell: Set-ScheduledTask shouldn't accept a plain text Password
Anyhow, the organization I work for, deals with the same general issue where the use-case is defined as: "how can we hide sensitive information as passwords used by invoked 3rd party applications in PowerShell scripts"
As suggested before: the problem in not due to any (PowerShell) scripting limitations but how the information (as plain text) is provided (input) to the script and how it is expected to be passed (output) to any other application.
To make this clear and to supply at least some (easy) solution, I have created an [HiddenString] class that might be used in a script to hide information as much as possible end-to-end inside the script itself.
class HiddenString {
hidden [SecureString]$SecureString = [SecureString]::new()
HiddenString([Object]$String) {
if ($String -is [SecureString]) { $This.SecureString = $String }
else {
foreach ($Character in [Char[]]$String) { $This.SecureString.AppendChar($Character) }
}
}
[String]Reveal(){
$Ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($This.SecureString)
$String = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($Ptr)
[System.Runtime.InteropServices.Marshal]::ZeroFreeCoTaskMemUnicode($Ptr)
Return $String
}
}
Note that I am using the SecureString type in the class not for better security but just for better hiding the concerned string.
Usage example:
function MyScript([String]$TaskName, [String]$UserName, [HiddenString]$Password) {
Start-Transcript -Path .\Transcript.txt
Write-Host "Scheduling $TaskName for $UserName/$Password" # Write-Log ...
Set-ScheduledTask -TaskName $TaskName -User $UserName -Password $Password.Reveal()
Stop-Transcript
}
Recommended invocation of MyScript:
$SecuredString = Read-Host 'Enter Password' -AsSecuredString
MyScript NotePad.Exe JohnDoe $SecuredString
Just hiding the sensitive information inside the MyScript:
$String = 'Sensitive Information'
MyScript NotePad.Exe JohnDoe $String
Transcript started, output file is .\Transcript.txt
Scheduling NotePad.Exe for JohnDoe/HiddenString
Transcript stopped, output file is .\Transcript.txt
Again, (I can't stress this enough):
warning: as a whole, this workaround is nothing more than security through obscurity
As Microsoft states themselves at SecureString shouldn't be used:
The general approach of dealing with credentials is to avoid them and instead rely on other means to authenticate, such as certificates or Windows authentication.
(which they should also do in their own cmdlets along with Set-ScheduledTask)
I have created an enhancement request for this idea:
#16921 Add [HiddenString] Class
You can register secrets with the agent to ensure they're scrubbed from the logs.
Write this to the output:
write-output "##vso[task.setsecret]THEVALUEYOUWANTHIDDEN"
This should register the secret with the agent. If you know your script will also popentially log the base64 value or another representation of the secret, make sure you register all permutations.
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
I'm trying to remove a Non-Microsoft MSP from a server that is running Win2k3 or Win2k8. The Update does show up in the Add/Remove programs when selecting "View Installed Updates". However I can't seem to find a way to get the MSP GUID.
I planned on using
msiexec /i {GUID-OF-PRODUCT} MSIPATCHREMOVE={GUID_OF_PATCH} /qb
that was found in this article: how to remove the Patch from console
However, I don't have a way to get the Patch GUID from the command line. Has anyone else been able to do something like this? There's plenty of ways to do this for Microsoft Patches, but since this is non-Microsoft, I'm hoping it's still possible.
Thanks,
Greg
You can use the Windows Installer com object to enumerate the patches.
Check out this article. It doesn't do exactly what you need, but it provides the comObject.types.ps1xml file you will need:
http://www.snowland.se/2010/02/21/read-msi-information-with-powershell/
Then you can do this to get the patch:
$installer_obj = New-Object -com WindowsInstaller.Installer;
$patches = $installer_obj.InvokeParamProperty("PatchesEx", "Product-Code-GUID", "s-1-1-0", 7, 15);
Product-Code-GUID is the GUID for the product you are interested in. I prefer to enumerate a list of products as well, and get the GUID programmatically based on the human readable name (i.e. the one that is displayed in Add/Remove Programs).
$installer_obj = New-Object -com WindowsInstaller.Installer;
$all_products = $installer_obj.GetProperty("Products");
foreach($product_code in $all_products) {
$product_name = $installer_obj.InvokeParamProperty("ProductInfo", $product_code, "ProductName")
if($product_name -eq "MySQL Server 5.1") {
$interesting_product_code = $product_code;
}
}
$patches = $installer_obj.InvokeParamProperty("PatchesEx", $interesting_product_code, "s-1-1-0", 7, 15);
Either route you take, now you just need to loop through the $patches and call msiexec from the command line with the proper arguments (if you are opting to use a literal string for the $interesting_product_code, just replace the variable and concatenation with the literal string GUID.):
foreach($patch in $patches) {
$patch_code = $patch.GetProperty("PatchCode");
$argument_list = "/I" + $interesting_product_code + " MSIPATCHREMOVE=$patch_code /qb /norestart";
Start-Process -FilePath "msiexec.exe" -ArgumentList $argument_list -Wait;
}
Here is a reference to the Windows Installer com object. You can do some other fun stuff with it too:
http://msdn.microsoft.com/en-us/library/windows/desktop/aa369432%28v=vs.85%29.aspx
Hope that helps,
Aaron
I am trying to create a file using powershell in a specific user context. E.g I have a user user01 on my local machine and I want to create a file in its context.
I am doing something like
New-Item c:\file.txt -Credential User01
It works but prompts me for password which I dont want it to. Is there any way I can accomplish this without having it prompt for password ?
The credential parameter on new-item is not actually supported for filesystems, so I'm not sure what you mean by "it works." It does NOT create the file as the passed user. In fact, the filesystem provider will say:
"The provider does not support the use of credentials. Perform the operation again without specifying credentials."
Taking an educated guess, I'd say you're trying to create a file with a different owner. PowerShell cannot do this on its own, so you'll need the following non-trivial script:
http://cosmoskey.blogspot.com/2010/07/setting-owner-on-acl-in-powershell.html
It works by enabling the SeBackup privilege for your security token (but you must already be an administrator.) This allows you to set any arbitrary owner on a file. Normally you can only change owner to administrators or your own account.
Oh, and this script is for powershell 2.0 only.
Rather than use a PowerShell cmdlet or .NET scripting on this one, you might take a look at the Windows utility takeown.exe. However, even it requires you supply the user's password that you're assigning ownership to.
Ok, I do start process in the user context and then create a file. Works like a charm.
Password, FilePath and UserName are passed in as arguments from command line.
$pw = convertto-securestring "$Password" -asplaintext –force
$credential = new-object -typename system.management.automation.pscredential -argumentlist "-default-",$pw
$localArgs = "/c echo>$FilePath"
[System.Diagnostics.Process]::Start("cmd", $localArgs, "$UserName", $credential.Password, "$Computer")
Or just make a call to SUBINACL.EXE? No need for password then.