I have an array of Credential objects and I would like to test that these credentials have permissions to write a file to a file share.
I was going to do something like
$myPath = "\\path\to\my\share\test.txt"
foreach ($cred in $credentialList)
{
"Testing" | Out-File -FilePath $myPath -Credential $cred
}
but then I discovered that Out-File doesn't take Credential as a parameter. What's the best way to solve this?
You can use New-PSDrive:
$myPath = "\\path\to\my\share"
foreach ($cred in $credentialList)
{
New-PSDrive Test -PSProvider FileSystem -Root $myPath -Credential $Cred
"Testing" | Out-File -FilePath Test:\test.txt
Remove-PSDrive Test
}
Here is asituation where an old exe (net.exe) seems to do better than powershell...
I guess you could try to map a network drive with the credential provided then test to write a file to that drive :
$cred=get-credential
$pass=$cred.GetNetworkCredential().Password
net use q: \\servername\share $pass /user:$cred.username
Use this script taken from Microsofts TechNet Script Center : http://gallery.technet.microsoft.com/scriptcenter/Lists-all-the-shared-5ebb395a
It is a lot easier to alter to fit your needs then to start completely from scratch.
Open up ListSharedFolderPermissions.ps1, and find the three $Properties vars. add a line at the top of each one so you can tell which user your looking at, so it should now look like this:
$Properties = #{'Username' = $Credential.UserName
'ComputerName' = $ComputerName
. . . . . }
Next, add your new Username property to the select-object line (3 times) :
$Objs|Select-Object Username,ComputerName,ConnectionStatus,SharedFolderName,SecurityPrincipal, `
FileSystemRights,AccessControlType
Once youve added those small pieces in the six appropriate places your script is ready to use:
cd c:\Path\where\you\put\ps1\file
$permissions = #()
$myPath = "computername"
foreach ($cred in $credentialList)
{
$permissions += .\ListAllSharedFolderPermission.ps1 -ComputerName $myPath -Credential $cred
$permissions += " "
}
$permissions | Export-Csv -Path "C:\Permission.csv" -NoTypeInformation
Try using the Invoke-Command function. It will take a credential object and allow you to run an arbitrary script block under that command. You can use that to test out writing the file
Invoke-Command -ScriptBlock { "Testing" | Out-File $myPath } -Credential $cred
I think the Invoke-command approach should work. But if nothing works you can try the powershell impersonation module. It successfully impersonates a user for most Powershell commands without the -Credential switch.
A few ideas:
Create your own PowerShell Provider
Impersonate a user and then write to the share (not sure if possible in powershell)
Use net use d:... as #Kayasax has suggested
Use WScript.Network
I'm very interested in the PowerShell provider myself, but I decided to make something real quick so I went with using the WScript.Network library. I used a hash table to track whether a user would be "authenticated" or not.
$credentials = #() # List of System.Net.NetworkCredential objects
$authLog = #{}
$mappedDrive = 'z:'
$tmpFile = $mappedDrive, '\', [guid]::NewGuid(), '.tmp' -join ''
$path = [io.path]::GetPathRoot('\\server\share\path')
$net = new-object -comObject WScript.Network
foreach ($c in $credentials) {
if ($authLog.ContainsKey($c.UserName)) {
# Skipping because we've already tested this user.
continue
}
try {
if (Test-Path $mappedDrive) {
$net.RemoveNetworkDrive($mappedDrive, 1) # 1 to force
}
# Attempt to map drive and write to it
$net.MapNetworkDrive($mappedDrive, $path, $false, $c.UserName, $c.Password)
out-file $tmpFile -inputObject 'test' -force
# Cleanup
Remove-Item $tmpFile -force
$net.RemoveNetworkDrive($mappedDrive, 1)
# Authenticated.
# We shouldn't have reached this if we failed to mount or write
$authLog.Add($c.UserName, 'Authorized')
}
catch [Exception] {
# Unathenticated
$authLog.Add($c.UserName, 'Unauthorized')
}
}
$authLog
# Output
Name Value
---- -----
desktop01\user01 Authorized
desktop01\user02 Unauthorized
Related
I am trying to figure out what I am doing wrong here. Very new to powershell so be gentle... Trying to run a PSSession on remote system (reading in from list of systems). Then trying to return the value for missing patches to my local system to then export to CSV. I am looking solely for a number to be returned. The value gets displayed in the Powershell window when inside the Invoke-command but then at the bottom of the script nothing is shown. Can anyone offer some advice how I can pass that value back to my system to then be able to export to a csv? Any advice would be greatly appreciated.
$array1 = Get-Content "C:\Users\******\Desktop\Server_List.txt"
$ReportResults = New-Object System.Collections.Generic.List[System.Object]
$Searchresult = #()
#parse thru each machine name in
foreach ($MachineName in $array1)
{
Write-host $MachineName
$session = New-PSSession -ComputerName $MachineName
Invoke-Command -Session $session {
Param($ReportResults)
#Get All Assigned updates in $SearchResult
$UpdateSession = New-Object -ComObject Microsoft.Update.Session
$UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
$SearchResult = $UpdateSearcher.Search("IsAssigned=1 and IsHidden=0 and
IsInstalled=0")
Write-Host "total=$($SearchResult.updates.count)"
$ReportResults.add($SearchResult)
} -ArgumentList $ReportResults
Remove-PSSession $session
}
$ReportResults # | export-csv C:\Users\******\Desktop\Compprogs\Test.csv -
Notypeinformation
You've to "mark" parameter as ref. See this link for further info.
Alternativelly you can return your desired value via Write-Output. Example:
$returnValue = Invoke-Command -ScriptBlock {
Write-Output "Hello World"
}
# $returnValue should include "Hello World"
Write-Host $returnValue
Be aware that when you use Write-Ouput multiple times at Invoke-Command $returnValue will include ALL values wrote to the output steram via Write-Ouput.
Hope that helps
The variable at the top of the script defines several commands/variables for New-PSDrive, as well as connection and installation.
After this, a function is created to open a text file and extract information out of it. I know this part works because I use it in 2 other scripts.
Lastly, The script executes the commands in the first variable.
The script will show as running successfully, but checking the remote computer reveals that nothing happened.
Prior to doing any of this activity, the remote computer has a script run against it that:
enables PSRemoting (setting firewall rules and starting WinRM), and
bypasses execution policies.
After those steps, the script below is run to install a piece of software.
$eAudIT2014V2Install = {
$eAudIT2014V2password = ConvertTo-SecureString "PasswordHere" -AsPlainText -Force
$eAudIT2014V2cred = New-Object System.Management.Automation.PSCredential('domain\user', $eAudIT2014V2password)
$eAudIT2014V2drive = New-PSDrive -Name eAudIT2014V2 -PSProvider FileSystem -Root "\\Server\Share" -Credential $eAudIT2014V2cred
$eAudIT2014V2job = Start-Job {"eAudIT2014V2:\Setup.cmd"}
Wait-Job $eAudIT2014V2job
Receive-Job $eAudIT2014V2job
}
Function Get-OpenFile($initialDirectory) {
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") |
Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.InitialDirectory = $initialDirectory
$OpenFileDialog.ShowDialog()
$OpenFileDialog.Filename
$OpenFileDialog.ShowHelp = $true
}
$InputFile = Get-OpenFile
if ($InputFile -eq "Cancel") {
Write-Host "Canceled By User"
exit
} else {
$Computers = #(Get-Content -Path $InputFile)
}
foreach ($computer in $computers) {
Write-Host "Installing eAudIT 2014V2 on Selected Computers"
Invoke-Command $eAudIT2014V2Install
}
I'm noticing that if I tell this script to run something basic like notepad.exe, a dllhost process starts on the machine, but notepad never does. What am I doing wrong?
The answer is pretty simple here. All of your script is for naught if you don't tell the Invoke-Command cmdlet what computer you want to execute the code on. As it is you are simply iterating a loop and invoking that command X number of times on the local machine. You need to change that second to the last line to specify the machine to execute the code on:
Invoke-Command $eAudIT2014V2Install -ComputerName $computer
I have the powershell script built and I'm getting a "Random" bit of output into the CSV file. The string is MailboxExport(and a number). It looks like a value that (Get-MailboxExportRequest).name would return but I can't see where I would pull something like that or how it is being inserted. I think I may have just been staring at it too long and I may just need a fresh pair of eyes to spot my mistake. I would go into what the script is trying to do but I've put quite a few notes in the script that should explain it fairly well.
################################################## PST Extraction Script ##################################################
# Completed October 2013 by Trey Nuckolls
#
# This script is meant to extract PST files from the Site 1 Exchange server at the Site2 site and deliver those PST
# files to a share on the Site2 network. The script will change the input CSV file to keep track of which PSTfiles have been
# extracted and when that occoured. The script will also set security on the PST file so only the user and IT administraion
# can access the PST file.
#
# To run this script, enter the username of the Site 1 domain account that you want to target for extraction of a PST file then
# Run the script. Can be run from any machine on the network as long as it is run by someone with domain admin rights on the
# Site 2 network. Powershell v2 or v3 is required to run the script.
#
#############################################################################################################################
$InPstPath = '\\Site1_Server\PST_Store'
$OutPstPath = '\\Site2_Server\PST_Store'
$AdminPath = '\\Site2_Server\PST_Store\Admin\'
#Container for Site1 username
$User = Get-Content $AdminPath'login.txt'
#Container for encrypted Site1 Password
$PWord = Cat $AdminPath'pass.txt' | ConvertTo-SecureString
#Credential package for accessing Site1 resouces
$Credentials = New-Object –TypeName System.Management.Automation.PSCredential –ArgumentList $User, $PWord
#Creation of Powershell Drives for use during session
New-PSDrive -Name Site1Share -PSProvider FileSystem -Root $InPstPath -Credential $Credentials
New-PSDrive -Name Site2Share -PSProvider FileSystem -Root $OutPstPath
#Container for Powershell session to Exchange server
$PSSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://Site1_Server/powershell -Credential $Credentials
#Creation of Powershell session to Site1 Exchange server, including import of exchange commandlets
Import-PSSession $PSSession
#Import of the CSV file that lists users to be targeted
$In_List = Invoke-Command {Import-Csv "\\Site1_Server\PST_Store\To_Be_Exported.csv"} -computername Site1_Server -Credential $Credentials
$Processed = foreach ($objUser in $In_List) {
if ($objUser.Completed -ne "Yes") {
$TargetUser = $objUser.name
$ShortDate = (Get-Date).toshortdatestring()
$SourceFile = "Site1Share:\$TargetUser.pst"
$DestinationFile = "Site2Share:\$TargetUser.pst"
#Export Mailbox to PST File
New-MailboxExportRequest -Mailbox $TargetUser -Filepath $InPstPath\$TargetUser.pst
do {Start-Sleep -Seconds 10}
until((Get-MailboxExportRequest -Status InProgress).count -eq 0)
#Copy PST File to PST Share
Copy-Item -Path $SourceFile -Destination $DestinationFile
#Add Security access on PST file (Target_User-Modify). Domain Admin-Full is inherited from parent.
$Acl = Get-Acl $DestinationFile
$Permission = "Site2_Domain\$TargetUser","Modify","Allow"
$AccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule $Permission
$Acl.SetAccessRule($AccessRule)
$Acl | Set-Acl $DestinationFile
#Remove PST file From Temporary area
Remove-Item -Path $SourceFile -Force
#Write back to checklist for new items that have just been processed
[PSCustomObject]#{Name=$TargetUser;Completed="Yes";Date=$ShortDate}
} else { if ($objUser.Completed -eq "Yes") {
#Passthrough of items that have already been completed
[PSCustomObject]#{Name=$objUser.name;Completed=$objUser.Completed;Date=$objUser.Date}}
}}
#Output the new version of the checklist
$Processed | export-csv -Path C:\TEMP\processed.csv
#Overwrite the old version checklist with the new one
Move-Item -Path C:\TEMP\processed.csv -Destination Site1Share:\To_Be_Exported.csv -force
#Cleanup PsDrives and PsSessions
Remove-PSDrive -Name Site1Share
Remove-PSDrive -Name Site2Share
Remove-PSSession -Session (Get-PSSession)
Input CSV is...
"Name","Completed","Date"
"User1","Yes","10/8/2013"
"User2","Yes","10/11/2013"
"User3",,
and output is...
"Name","Completed","Date"
"User1","Yes","10/8/2013"
"User2","Yes","10/11/2013"
"MailboxExport7",,
"User3","Yes","10/11/2013"
It is indeed very likely that the issue is caused by New-MailboxExportRequest, as you already suspected. The cmdlet prints information about the created object, which lumped together with the rest of the output you create in the loop, and then assigned to the variable $Processed.
To avoid this you can suppress the cmdlet output like this:
New-MailboxExportRequest -Mailbox ... | Out-Null
or like this:
New-MailboxExportRequest -Mailbox ... >$null
Assigning the output to a variable should work as well:
$exportRequest = New-MailboxExportRequest -Mailbox ...
On you Export-CSV, try adding the flag: "-NoTypeInformation"
I think this may be some sort of name space crossover issue between the custom object and another existing object (probably the mailboxexportrequest object on the exchange server). After messing around with this for a while I was able to get it to fail in a new way where the resultant csv file was full of details from the mailbox exports and their was a 'name' column that also had listed the usernames. I changed the hashes on the input csv from 'name to 'username' and the resultant MailboxExport entries have ceased. There are now blank row but I'm certainly willing to live with that imperfection as it doesn't break this (short lived) process.
If anyone has any insight into the root cause I'd certainly love to hear what it is but I think I've figured out a solution to the point that I can live with.
I have a script that I am setting up to do some migration of users' Exchange mailboxes into .pst file. The idea was that I would have a CSV file that I could put users' names on and then when the script kicked off nightly it would open the CSV file and find users that have been added, perform the requested actions on those users accounts (export, move set permissions etc) and then write back to the CSV file Marking those users as completed and writing the date on which they were completed. Here is what I have so far.
$InPstPath = '\\server1\PST_Store\'
$OutPstPath = '\\server2\PST_Store\'
$User = Get-Content $OutPstPath'login.txt'
$PWord = cat $OutPstPath'pass.txt' | convertto-securestring
$Credentials = New-Object –TypeName System.Management.Automation.PSCredential –ArgumentList $User, $PWord
$PSSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://Server1/powershell -Credential $Credentials
Import-PSSession $PSSession
$In_List = Invoke-Command {Import-Csv "\\Server1\PST_Store\Admin\To_Be_Exported.csv"} -computername Server1 -Credential $Credentials
foreach ($objUser in $In_List) {
if ($objUser.Completed -ne "Yes") {
$TargetUser = $objUser.name
$ShortDate = (Get-Date).toshortdatestring()
New-MailboxExportRequest -Mailbox $TargetUser -Filepath "$InPstPath$TargetUser.pst"
$objUser.Completed = "Yes"
$objUser.Date = $ShortDate
}
}
Remove-PSSession -Session (Get-PSSession)
I can't figure out a decent way to write back the $objUser.Completed and $objUser.Date values to the CSV.
Firstly, it's obvious but let me state it anyway. The very first time you run this script, $objUser.name, $objUser.Completed and $objUser.Date will not exist; So, the line
$TargetUser=$objUser.name
will not work, unless you actually have the structure in place in that csv (i.e. have the headers name,completed,date).
Now assuming you got that part done, all you have to do is to create an object that captures the state in an object and then write that back.
$Processed = foreach ($objUser in $In_List) {
if ($objUser.Completed -ne "Yes") {
$TargetUser = $objUser.name
$ShortDate = (Get-Date).toshortdatestring()
New-MailboxExportRequest -Mailbox $TargetUser -Filepath "$InPstPath$TargetUser.pst"
[PSCustomObject]#{Name=$objUser.name;Completed="Yes";Date=$ShortDate}
}
} else {
[PSCustomObject]#{Name=$objUser.name;Completed="No";Date=$null}
}
## export to a temp file
$Processed | export-csv -Path $env:TEMP\processed.csv
## You should probably check to see if original file was modified since you started working on it
# and copy over if not
Copy-Item $env:TEMP\processed.csv $OutPstPath'login.txt' -force
I just burned a couple of hours searching for a solution to send files over an active PSSession. And the result is nada, niente. I'm trying to invoke a command on a remote computer over an active session, which should copy something from a network storage. So, basically this is it:
icm -Session $s {
Copy-Item $networkLocation $PCLocation }
Because of the "second hop" problem, I can't do that directly, and because I'm running win server 2003 I cant enable CredSSP. I could first copy the files to my computer and then send/push them to the remote machine, but how? I tried PModem, but as I saw it can only pull data and not push.
Any help is appreaciated.
This is now possible in PowerShell / WMF 5.0
Copy-Item has -FromSession and -toSession parameters. You can use one of these and pass in a session variable.
eg.
$cs = New-PSSession -ComputerName 169.254.44.14 -Credential (Get-Credential) -Name SQL
Copy-Item Northwind.* -Destination "C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQL2008R2\MSSQL\DATA\" -ToSession $cs
See more examples at here, or you can checkout the official documentation.
If it was a small file, you could send the contents of the file and the filename as parameters.
$f="the filename"
$c=Get-Content $f
invoke-command -session $s -script {param($filename,$contents) `
set-content -path $filename -value $contents} -argumentlist $f,$c
If the file is too long to fit in whatever the limits for the session are, you could read the file in as chunks, and use a similar technique to append them together in the target location
PowerShell 5+ has built-in support for doing this, described in David's answer.
I faced the same problem a while ago and put together a proof-of-concept for sending files over a PS Remoting session. You'll find the script here:
https://gist.github.com/791112
#requires -version 2.0
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string]
$ComputerName,
[Parameter(Mandatory=$true)]
[string]
$Path,
[Parameter(Mandatory=$true)]
[string]
$Destination,
[int]
$TransferChunkSize = 0x10000
)
function Initialize-TempScript ($Path) {
"<# DATA" | Set-Content -Path $Path
}
function Complete-Chunk () {
#"
DATA #>
`$TransferPath = `$Env:TEMP | Join-Path -ChildPath '$TransferId'
`$InData = `$false
`$WriteStream = [IO.File]::OpenWrite(`$TransferPath)
try {
`$WriteStream.Seek(0, 'End') | Out-Null
`$MyInvocation.MyCommand.Definition -split "``n" | ForEach-Object {
if (`$InData) {
`$InData = -not `$_.StartsWith('DATA #>')
if (`$InData) {
`$WriteBuffer = [Convert]::FromBase64String(`$_)
`$WriteStream.Write(`$WriteBuffer, 0, `$WriteBuffer.Length)
}
} else {
`$InData = `$_.StartsWith('<# DATA')
}
}
} finally {
`$WriteStream.Close()
}
"#
}
function Complete-FinalChunk ($Destination) {
#"
`$TransferPath | Move-Item -Destination '$Destination' -Force
"#
}
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
$EncodingChunkSize = 57 * 100
if ($EncodingChunkSize % 57 -ne 0) {
throw "EncodingChunkSize must be a multiple of 57"
}
$TransferId = [Guid]::NewGuid().ToString()
$Path = ($Path | Resolve-Path).ProviderPath
$ReadBuffer = New-Object -TypeName byte[] -ArgumentList $EncodingChunkSize
$TempPath = ([IO.Path]::GetTempFileName() | % { $_ | Move-Item -Destination "$_.ps1" -PassThru}).FullName
$Session = New-PSSession -ComputerName $ComputerName
$ReadStream = [IO.File]::OpenRead($Path)
$ChunkCount = 0
Initialize-TempScript -Path $TempPath
try {
do {
$ReadCount = $ReadStream.Read($ReadBuffer, 0, $EncodingChunkSize)
if ($ReadCount -gt 0) {
[Convert]::ToBase64String($ReadBuffer, 0, $ReadCount, 'InsertLineBreaks') |
Add-Content -Path $TempPath
}
$ChunkCount += $ReadCount
if ($ChunkCount -ge $TransferChunkSize -or $ReadCount -eq 0) {
# send
Write-Verbose "Sending chunk $TransferIndex"
Complete-Chunk | Add-Content -Path $TempPath
if ($ReadCount -eq 0) {
Complete-FinalChunk -Destination $Destination | Add-Content -Path $TempPath
Write-Verbose "Sending final chunk"
}
Invoke-Command -Session $Session -FilePath $TempPath
# reset
$ChunkCount = 0
Initialize-TempScript -Path $TempPath
}
} while ($ReadCount -gt 0)
} finally {
if ($ReadStream) { $ReadStream.Close() }
$Session | Remove-PSSession
$TempPath | Remove-Item
}
Some minor changes would allow it to accept a session as a parameter instead of it starting a new one. I found the memory consumption on the Remoting service on the destination computer could grow quite large when transferring large files. I suspect PS Remoting wasn't really designed to be used this way.
NET USE allows you to add a local drive letter for the remote system, which then then allows you to use the drive letter in your PSSession, or even without a PSSession. This is helpful if you don't have Powershell v5.0, and even if you do,
You may use the remote machine name or its IP address as part of the remote UNC path and you can specify the username and password credentials on the same line:
NET USE Z: \\192.168.1.50\ShareName /USER:192.168.1.50\UserName UserPassword
Another example:
NET USE Z: \\RemoteSystem\ShareName /USER:RemoteSystem\UserName UserPassword
OR
NET USE Z: \\RemoteSystem\ShareName /USER:Domain\UserName UserPassword
If you don't supply the user credentials on the same line, you will be prompted for them:
>NET USE Z: \\192.168.1.50\ShareName
Enter the user name for '192.168.1.50': 192.168.1.50\UserName
Enter the password for 192.168.1.50: *****
The command completed successfully.
You may remove the drive letter when you're finished with the following:
NET USE Z: /delete
You can get the full syntax with NET USE /?
>net use /?
The syntax of this command is:
NET USE
[devicename | *] [\\computername\sharename[\volume] [password | *]]
[/USER:[domainname\]username]
[/USER:[dotted domain name\]username]
[/USER:[username#dotted domain name]
[/SMARTCARD]
[/SAVECRED]
[[/DELETE] | [/PERSISTENT:{YES | NO}]]
NET USE {devicename | *} [password | *] /HOME
NET USE [/PERSISTENT:{YES | NO}]
NET is a standard external .exe command in the system folder and works in Powershell just fine.
$data = Get-Content 'C:\file.exe' -Raw
Invoke-Command -ComputerName 'server' -ScriptBlock { $using:data | Set-Content -Path 'D:\filecopy.exe' }
Don't actually know what the maximum file size limitation is.