There's an .mdb located on a shared drive in my organization that's password protected and needs to stay password protected. I have the password. I use a local copy for data analysis. The shared drive is very slow for me so I copy it daily using powershell. I need to remove the password manually because the analysis software available to me doesn't support .mdb passwords.
The code below successfully opens the .mdb so I can manually unset the password, but I'd like to automate unsetting the password.
$path = "C:\path\to\db\"
$file = "db.mdb"
$access = New-Object -com Access.Application
$access.Visible = $True
$access.OpenCurrentDataBase($path + $file, $True, "password")
I think my problem is I don't understand PowerShell's implementation of the object model. I want to use Database.NewPassword to change the password. In VB implementations it requires a database object, however, and when I substitute $db = $access.OpenCurrentDataBase($path + $file, $True, "password") for $access.OpenCurrentDataBase($path + $file, $True, "password") the $db variable is $null.
Is there a way to refer to the database object in PowerShell?
You can manage the database password with Access DDL.
ALTER DATABASE PASSWORD newpassword oldpassword
This is how I removed the password with PowerShell:
$path = "C:\Temp\DbPwd_foo.mdb"
$newpassword = "Null"
$oldpassword = "foo"
$accessApp = new-object -com access.application
$accessApp.OpenCurrentDatabase($path, -1, $oldpassword)
$sql = "ALTER DATABASE PASSWORD " + $newpassword + " " + $oldpassword
$accessApp.CurrentProject.Connection.Execute($sql)
$accessApp.Quit()
You can restore the previous password by swapping the values of $newpassword and $oldpassword, and calling OpenCurrentDatabase without its password argument.
You can use DAO for that, the DbEngine.Compact method. It's a pain that powershell does not allow naming parameters and skipping the optionals, though, unless you want to do complicated stuff.
$path = "C:\path\to\db\"
$file = "db.mdb"
$newfile = "decrypted.mdb"
$dbe= New-Object -com DAO.DbEngine.120
$dbe.Compact($path + $file, $path + $newfile, ";LANGID=0x0409;CP=1252;COUNTRY=0", 64, ";PWD=password")
Related
I was doing something like described in this post to save credentials in a secured file so our automated process can use that to run remote PS scripts via Invoke-command:
http://blogs.technet.com/b/robcost/archive/2008/05/01/powershell-tip-storing-and-using-password-credentials.aspx
This works great when I run this under my account - password is read from encrypted file, passed to Invoke-command and everything is fine.
Today, when my script was ready for its prime time, I tried to run it under windows account that will be used by automated process and got this error below while my script was trying to read secured password from a file:
ConvertTo-SecureString : Key not valid for use in specified state.
At \\remoted\script.ps1:210 char:87
+ $password = get-content $PathToFolderWithCredentials\pass.txt | convertto-sec
urestring <<<<
+ CategoryInfo : InvalidArgument: (:) [ConvertTo-SecureString], C
ryptographicException
+ FullyQualifiedErrorId : ImportSecureString_InvalidArgument_Cryptographic
Error,Microsoft.PowerShell.Commands.ConvertToSecureStringCommand
Asked my workmate to run under his account and he got the same error.
This is the code I am using to save credentials:
$PathToFolderWithCredentials = "\\path\removed"
write-host "Enter login as domain\login:"
read-host | out-file $PathToFolderWithCredentials\login.txt
write-host "Enter password:"
read-host -assecurestring | convertfrom-securestring | out-file $PathToFolderWithCredentials\pass.txt
write-host "*** Credentials have been saved to $pathtofolder ***"
This is the code in the script to run by automated process to read them to use in Invoke-command:
$login= get-content $PathToFolderWithCredentials\login.txt
$password = get-content $PathToFolderWithCredentials\pass.txt | convertto-securestring
$credentials = new-object -typename System.Management.Automation.PSCredential -argumentlist $login,$password
Error happens on line $password = get-content $PathToFolderWithCredentials\pass.txt | convertto-securestring
Any ideas?
You have to create the password string on the same computer and with the same login that you will use to run it.
ConvertFrom-SecureString takes a Key ( and SecureKey) parameter. You can specify the key to save the encrypted standard string and then use the key again in ConvertTo-SecureString to get back the secure string, irrespective of the user account.
http://technet.microsoft.com/en-us/library/dd315356.aspx
In a project, I have implemented asymmetric encryption, whereby people encrypt the password using the public key and the automation process has the private key to decrypt passwords: Handling passwords in production config for automated deployment
The below will allow credentials to be saved as a file, then those credentials to be used by another script being run by a different user, remotely.
The code was taken from a great article produced by David Lee, with only some minor adjustments from myself https://blog.kloud.com.au/2016/04/21/using-saved-credentials-securely-in-powershell-scripts/
First step is to save a a secure password to a file using AES. The below will run as a stand alone script:
# Prompt you to enter the username and password
$credObject = Get-Credential
# The credObject now holds the password in a ‘securestring’ format
$passwordSecureString = $credObject.password
# Define a location to store the AESKey
$AESKeyFilePath = “aeskey.txt”
# Define a location to store the file that hosts the encrypted password
$credentialFilePath = “credpassword.txt”
# Generate a random AES Encryption Key.
$AESKey = New-Object Byte[] 32
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($AESKey)
# Store the AESKey into a file. This file should be protected! (e.g. ACL on the file to allow only select people to read)
Set-Content $AESKeyFilePath $AESKey # Any existing AES Key file will be overwritten
$password = $passwordSecureString | ConvertFrom-SecureString -Key $AESKey
Add-Content $credentialFilePath $password
Then in your script where you need to use credentials use the following:
#set up path and user variables
$AESKeyFilePath = “aeskey.txt” # location of the AESKey
$SecurePwdFilePath = “credpassword.txt” # location of the file that hosts the encrypted password
$userUPN = "domain\userName" # User account login
#use key and password to create local secure password
$AESKey = Get-Content -Path $AESKeyFilePath
$pwdTxt = Get-Content -Path $SecurePwdFilePath
$securePass = $pwdTxt | ConvertTo-SecureString -Key $AESKey
#crete a new psCredential object with required username and password
$adminCreds = New-Object System.Management.Automation.PSCredential($userUPN, $securePass)
#use the $adminCreds for some task
some-Task-that-needs-credentials -Credential $adminCreds
Please be aware that if the user can get access to the password file and the key file, they can decrypt the password for the user.
Another approach would be to protect the data using scope 'LocalMachine' instead of 'CurrentUser' which is the one used by ConvertFrom-SecureString.
public static string Protect(SecureString input, DataProtectionScope dataProtectionScope = DataProtectionScope.CurrentUser, byte[] optionalEntropy = null)
{
byte[] data = SecureStringToByteArray(input);
byte[] data2 = ProtectedData.Protect(data, optionalEntropy, dataProtectionScope);
for (int i = 0; i < data.Length; i++)
{
data[i] = 0;
}
return ByteArrayToString(data2);
}
private static byte[] SecureStringToByteArray(SecureString s)
{
var array = new byte[s.Length * 2];
if (s.Length > 0)
{
IntPtr intPtr = Marshal.SecureStringToGlobalAllocUnicode(s);
try
{
Marshal.Copy(intPtr, array, 0, array.Length);
}
finally
{
Marshal.FreeHGlobal(intPtr);
}
}
return array;
}
private static string ByteArrayToString(byte[] data)
{
var stringBuilder = new StringBuilder();
for (int i = 0; i < data.Length; i++)
{
stringBuilder.Append(data[i].ToString("x2", CultureInfo.InvariantCulture));
}
return stringBuilder.ToString();
}
The encrypted string can be used by ConvertTo-SecureString which is using scope 'CurrentUser'.
Assuming you have a known list of N users who will use the credentials (e.g. one developer userMe and a system/service user userSys) you can just (get those users to) make N copies of the pass.txt file: one for each user.
So the password of userX will result in e.g. 2 *.pass.txt files:
userX.userMe.pass.txt
userX.userSys.pass.txt
When userMe wants the creds he/she reads userX.userMe.pass.txt etc.
I accidentally deleted 180 users from my AD and they aren't recoverable. I have recreated the accounts in AD and what not. This creates a new profile on their laptops when they login because of the new SID. I'm trying to write a script that grants them access to their old profile folder and create a shortcut on their desktop that leads there.
I've got the script working fine with one problem. The environment variables that are used, end up referring back to the admin account that runs the script. The users themselves don't have permission to change security on their old folder. I need to try and have the environment variables refer to the user yet have the privilege of an admin account to rewrite the permissions.
Here is the script so far.. I'm deploying this with Task Scheduler at the moment, which is another can of worms in that I'm not entirely understanding of the credential side of things there. I mean ideally, the task would run as a domain admin, execute the script asap, and have the script resolve the environment variables to the logged on user.
$permission = ":(OI)(CI)M"
$sam = $env:USERNAME
$folderName = "C:\Users\$($sam)"
Invoke-Expression -Command ( 'ICACLS $folderName /grant:r $sam$($permission) /t' )
$WshShell = New-Object -comObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("$Home\Desktop\Profile Backup.lnk")
$Shortcut.TargetPath = $folderName
$Shortcut.Save()
Its the $env:USERNAME and $home variables that are giving me trouble..
Or is there another way I should be tackling this problem?
You could use query session command to get the login name of the current logged on user. Then create NTAccount object based on that to retrieve SID and win32_userprofile WMI object to find out the profile path. Like this:
$m = query session | Select-String -Pattern "\>console\s*(\S*)\s"
$sam = $m.Matches[0].Groups[1].value
$acc = New-Object System.Security.Principal.NTAccount($sam)
$sid = $acc.Translate([System.Security.Principal.SecurityIdentifier]).Value
$profile = Get-CimInstance -ClassName win32_userprofile -Filter "SID='$sid'"
$folderName = $profile.LocalPath
Edit I have given it second thought over-night so I'll update the answer. You will be required to have domain admin password encrypted and then users will run the script.
It always sucks when something like this happens. I don't have a possibility to try this out, but I think the following approach would be feasible. The script asks user for password encrypts it and run the command as the user.
First phase would be to have a domain admin to encrypt his password to a file:
This is to be prepared by Domain Admin (distributed with the PS script) - I recommend changing password after the recovery is complete:
1) Read-Host -AsSecureString | ConvertFrom-SecureString | Out-File 'C:\<script_path>\admin_passwd.txt'
2) This is to be executed by user (you have to fill in the admin user id and have the password file distributed with the script). The script path can be obtained by (Get-Location).Path. I'm not adding it into the source code so you can decide how to implement it:
$permission = ":(OI)(CI)M"
$admin= "<your_admin_userid>"
$sam = $env:USERNAME
$domain = $env:UserDomain
$folderName = "C:\Users\$($sam)"
# get domain admin password
$encrypted_passwd = get-content 'C:\<script_path>\admin_passwd.txt' | ConvertTo-securestring
# Setting process invocation parameters.
$process_start_info = New-Object -TypeName System.Diagnostics.ProcessStartInfo
$process_start_info.CreateNoWindow = $true
$process_start_info.UseShellExecute = $false
$process_start_info.RedirectStandardOutput = $true
$process_start_info.RedirectStandardError = $true
$process_start_info.UserName = $admin
$process_start_info.Domain = $domain
$process_start_info.Password = $encrypted_passwd
$process_start_info.Verb = 'runas'
$process_start_info.FileName = 'ICACLS'
$process_start_info.Arguments = "$folderName /grant:r $sam$($permission) /t"
# Creating process object.
$process = New-Object -TypeName System.Diagnostics.Process
$process.StartInfo = $process_start_info
# Start the process
[Void]$process.Start()
$process.WaitForExit()
# synchronous output - captures everything
$output = $process.StandardOutput.ReadToEnd()
$output += $process.StandardError.ReadToEnd()
Write-Output $output
$WshShell = New-Object -comObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("$Home\Desktop\Profile Backup.lnk")
$Shortcut.TargetPath = $folderName
$Shortcut.Save()
anyone know how to rewrite below code, so it can invoke sql script. For instance, instead of put 'SELECT ##SERVERNAME AS ServerName' in the powershell script, I would like to put it into sql file.
$Path = "D:\AdminStuff\PowerShell\Password\Password.txt"
$uid = 'sa'
$pwd = Get-Content D:\AdminStuff\PowerShell\Password\Password.txt | ConvertTo-SecureString
$pwd.MakeReadOnly()
$creds = New-Object System.Data.SqlClient.SqlCredential($uid,$pwd)
$con = New-Object System.Data.SqlClient.SqlConnection
$con.ConnectionString = "Server=SLB-CLMFZ52;Database=master;"
$con.Credential = $creds
$con.Open()
$sql = "SELECT ##SERVERNAME AS ServerName"
$cmd = New-Object System.Data.SqlClient.SqlCommand($sql,$con)
$rdr = $cmd.ExecuteReader()
while($rdr.Read())
{
$rdr["ServerName"].ToString()
}
$con.Close()
While you can use the SqlCredential, SqlConnection, and SqlCommand .NET classes as you show in your question, there are simpler alternatives. It is a lot less work, for example, to use the Invoke-Sqlcmd cmdlet instead. Invoke-Sqlcmd is essentially the venerable sqlcmd utility with a PowerShell disguise. Thus, assuming your query was in a file myScript.sql, you can just run this--the InputFile parameter provides the means to store your SQL in a separate file as you requested:
$InvokeParams = #{
Server = 'SLB-CLMFZ52'
Database = 'Master'
Username = 'sa'
Password = 'xyz'
InputFile = 'myScript.sql'
}
Invoke-SqlCmd #InvokeParams
Now the obvious problem with that, though, is the password must be in plain text. (Of course, the password in the OP was in plain text in a file, as well. :-) Unfortunately Invoke-Sqlcmd does not work with PowerShell credential objects, which would have made it a lot more secure. The only reasonable workaround to get some security is to use Windows Authentication instead of SQL authentication. Then you could, for example, omit the username and password, and the query will be invoked with secure credentials:
$InvokeParams = #{
Server = 'SLB-CLMFZ52'
Database = 'Master'
InputFile = 'myScript.sql'
}
Invoke-SqlCmd #InvokeParams
To use the cmdlet just Import-Module sqlps. For the basics of Invoke-SqlCmd take a look at TechNet and for a more in-depth treatment, including the vagaries of sqlps, see part 1 of my Practical PowerShell for SQL Server Developers and DBAs article.
What about something simple like this:
$sql=get-content $filename
I'm trying to use WinSCP .NET assembly with secure credential, when the password secured in external file.
# Load WinSCP .NET assembly
Add-Type -Path "D:\WinSCPnet.dll"
# Setup session options
$sessionOptions = New-Object WinSCP.SessionOptions
$sessionOptions.Protocol = [WinSCP.Protocol]::Sftp
# Env
$sessionOptions.HostName = "blabla.com"
$sessionOptions.UserName = "UUUU"
#$sessionOptions.Password = "PPPP"
$sessionOptions.SshHostKeyFingerprint = "XXXXXXXXX"
$remotePath = "/home/UUUU/"
With hard coded password its working. If I would like to use securestring for the password, how should I do that?
I tried:
read-host -assecurestring | convertfrom-securestring | out-file D:\securestring.txt
To keep the secure password in a file. Then, in my script, I get it back:
$sessionOptions.Password = get-Content D:\securestring.txt | convertto-securestring
$Cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $sessionOptions.UserName, $sessionOptions.Password}
But it's not working...
Since WinSCP 5.7, the assembly accepts the SecureString using the SessionOptions.SecurePassword.
See https://winscp.net/eng/docs/library_sessionoptions
Thanks for inspiring this improvement.
While the assembly keeps the password encrypted in memory, it still needs to decrypt it eventually. Improvements to limit internal copies of the decrypted password are pending.
As #Matt pointed out, the WinSCP .Net assembly doesn't accept secure strings or credential objects. You need to pass the password as a plaintext string. You can store the secure string in a file, though:
Read-Host 'Enter password' -AsSecureString |
ConvertFrom-SecureString |
Out-File 'C:\password.txt'
and use a PSCredential object to retrieve the decrypted password from the secure string after you read it from the file:
$un = 'username'
$pw = Get-Content 'C:\password.txt' | ConvertTo-SecureString
$cred = New-Object Management.Automation.PSCredential $un, $pw
try {
Add-Type -Path 'WinSCPnet.dll'
$opt = New-Object WinSCP.SessionOptions
$opt.Protocol = [WinSCP.Protocol]::Sftp
$opt.HostName = 'example.org'
$opt.UserName = $cred.UserName
$opt.Password = $cred.GetNetworkCredential().Password
$opt.SshHostKeyFingerprint = 'ssh-rsa 2048 ...'
$sftp = New-Object WinSCP.Session
$sftp.Open($opt)
...
} catch {
...
} finally {
if ($sftp) { $sftp.Dispose() }
}
WinSCP sample code taken from the documentation.
Note, however, that the password must be saved by the same user who is running the SFTP PowerShell script, because the encryption key is derived from that user's password.
According to WinSCP the password property just supports a string. So trying to pass a secure string will not work. If you really want to store the password in a file, you could attempt to store it as the secure string but that is a really bad idea in general since it can be unsecured just as easily (Also not sure if it is possible). I recommend the following option.
# Only stored in memory which is safer.
$sessionOptions.Password = read-host
If you have your heart set on something else you could try this. Just know for previous reasons I do not condone this. Also i have to see if it even works because it looks like you cant output securestring to file.
read-host | out-file D:\securestring.txt
$sessionOptions.Password = get-Content D:\securestring.txt
Ansgar's explains what I didn't know was possible. You can stored the secure string in a file and use it elsewhere.
I was doing something like described in this post to save credentials in a secured file so our automated process can use that to run remote PS scripts via Invoke-command:
http://blogs.technet.com/b/robcost/archive/2008/05/01/powershell-tip-storing-and-using-password-credentials.aspx
This works great when I run this under my account - password is read from encrypted file, passed to Invoke-command and everything is fine.
Today, when my script was ready for its prime time, I tried to run it under windows account that will be used by automated process and got this error below while my script was trying to read secured password from a file:
ConvertTo-SecureString : Key not valid for use in specified state.
At \\remoted\script.ps1:210 char:87
+ $password = get-content $PathToFolderWithCredentials\pass.txt | convertto-sec
urestring <<<<
+ CategoryInfo : InvalidArgument: (:) [ConvertTo-SecureString], C
ryptographicException
+ FullyQualifiedErrorId : ImportSecureString_InvalidArgument_Cryptographic
Error,Microsoft.PowerShell.Commands.ConvertToSecureStringCommand
Asked my workmate to run under his account and he got the same error.
This is the code I am using to save credentials:
$PathToFolderWithCredentials = "\\path\removed"
write-host "Enter login as domain\login:"
read-host | out-file $PathToFolderWithCredentials\login.txt
write-host "Enter password:"
read-host -assecurestring | convertfrom-securestring | out-file $PathToFolderWithCredentials\pass.txt
write-host "*** Credentials have been saved to $pathtofolder ***"
This is the code in the script to run by automated process to read them to use in Invoke-command:
$login= get-content $PathToFolderWithCredentials\login.txt
$password = get-content $PathToFolderWithCredentials\pass.txt | convertto-securestring
$credentials = new-object -typename System.Management.Automation.PSCredential -argumentlist $login,$password
Error happens on line $password = get-content $PathToFolderWithCredentials\pass.txt | convertto-securestring
Any ideas?
You have to create the password string on the same computer and with the same login that you will use to run it.
ConvertFrom-SecureString takes a Key ( and SecureKey) parameter. You can specify the key to save the encrypted standard string and then use the key again in ConvertTo-SecureString to get back the secure string, irrespective of the user account.
http://technet.microsoft.com/en-us/library/dd315356.aspx
In a project, I have implemented asymmetric encryption, whereby people encrypt the password using the public key and the automation process has the private key to decrypt passwords: Handling passwords in production config for automated deployment
The below will allow credentials to be saved as a file, then those credentials to be used by another script being run by a different user, remotely.
The code was taken from a great article produced by David Lee, with only some minor adjustments from myself https://blog.kloud.com.au/2016/04/21/using-saved-credentials-securely-in-powershell-scripts/
First step is to save a a secure password to a file using AES. The below will run as a stand alone script:
# Prompt you to enter the username and password
$credObject = Get-Credential
# The credObject now holds the password in a ‘securestring’ format
$passwordSecureString = $credObject.password
# Define a location to store the AESKey
$AESKeyFilePath = “aeskey.txt”
# Define a location to store the file that hosts the encrypted password
$credentialFilePath = “credpassword.txt”
# Generate a random AES Encryption Key.
$AESKey = New-Object Byte[] 32
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($AESKey)
# Store the AESKey into a file. This file should be protected! (e.g. ACL on the file to allow only select people to read)
Set-Content $AESKeyFilePath $AESKey # Any existing AES Key file will be overwritten
$password = $passwordSecureString | ConvertFrom-SecureString -Key $AESKey
Add-Content $credentialFilePath $password
Then in your script where you need to use credentials use the following:
#set up path and user variables
$AESKeyFilePath = “aeskey.txt” # location of the AESKey
$SecurePwdFilePath = “credpassword.txt” # location of the file that hosts the encrypted password
$userUPN = "domain\userName" # User account login
#use key and password to create local secure password
$AESKey = Get-Content -Path $AESKeyFilePath
$pwdTxt = Get-Content -Path $SecurePwdFilePath
$securePass = $pwdTxt | ConvertTo-SecureString -Key $AESKey
#crete a new psCredential object with required username and password
$adminCreds = New-Object System.Management.Automation.PSCredential($userUPN, $securePass)
#use the $adminCreds for some task
some-Task-that-needs-credentials -Credential $adminCreds
Please be aware that if the user can get access to the password file and the key file, they can decrypt the password for the user.
Another approach would be to protect the data using scope 'LocalMachine' instead of 'CurrentUser' which is the one used by ConvertFrom-SecureString.
public static string Protect(SecureString input, DataProtectionScope dataProtectionScope = DataProtectionScope.CurrentUser, byte[] optionalEntropy = null)
{
byte[] data = SecureStringToByteArray(input);
byte[] data2 = ProtectedData.Protect(data, optionalEntropy, dataProtectionScope);
for (int i = 0; i < data.Length; i++)
{
data[i] = 0;
}
return ByteArrayToString(data2);
}
private static byte[] SecureStringToByteArray(SecureString s)
{
var array = new byte[s.Length * 2];
if (s.Length > 0)
{
IntPtr intPtr = Marshal.SecureStringToGlobalAllocUnicode(s);
try
{
Marshal.Copy(intPtr, array, 0, array.Length);
}
finally
{
Marshal.FreeHGlobal(intPtr);
}
}
return array;
}
private static string ByteArrayToString(byte[] data)
{
var stringBuilder = new StringBuilder();
for (int i = 0; i < data.Length; i++)
{
stringBuilder.Append(data[i].ToString("x2", CultureInfo.InvariantCulture));
}
return stringBuilder.ToString();
}
The encrypted string can be used by ConvertTo-SecureString which is using scope 'CurrentUser'.
Assuming you have a known list of N users who will use the credentials (e.g. one developer userMe and a system/service user userSys) you can just (get those users to) make N copies of the pass.txt file: one for each user.
So the password of userX will result in e.g. 2 *.pass.txt files:
userX.userMe.pass.txt
userX.userSys.pass.txt
When userMe wants the creds he/she reads userX.userMe.pass.txt etc.