Problem with import variable from text file and loop it - powershell

I'm trying to code a script with PowerShell and I'm stuck. Maybe you can send me a link or give a tip?
I write a command in PowerShell and I try to execute it on many servers with different domains:
$Username = 'domain\user'
$Password = 'password'
$pass = ConvertTo-SecureString -AsPlainText $Password -Force
$Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username,$pass
invoke-command -filepath \source_script.ps1 -computerName server07 -Credential $cred
i have a long list in serverlist.txt file something like this
city servername credential domain
city servername credential domain
city servername credential domain
and now im trying to do my script on many server in list, but not all server in list. For example, do this script only where city = berlin or london or moscow.
What should i do? How to load variable to $username $ password in loop.
I know how to do it with BATCH with powershell, but i must do it in PowerShell
Look at my batch file, how i do it before:
FOR /F %%a IN (\my_list.txt) DO TYPE \server_list.txt | find.exe "%%a" >> %source_path%\temporary_list.txt
::my_list.txt is the list all off my variables
for /f "tokens=*" %%A in (\temporary_list.txt) do call :run_program %%A
:run_program
SET mycity=%1
SET myserver=%2
SET mycredentials=%3
SET mydomain=%4
psExec.exe -u %mydomain% %mycredentials% \\%myserver% blah blah blah, rest of script

I would do it like this. First of all, import you server list, then iterate over it to decide which servers to work on...
#import your server list
$serverlist = Get-Content -path C:\path\to\serverlist.txt
#iterate over $serverlist, and only act on entries that match Berlin or London or Moscow, etc...
Foreach ($server in $serverlist | Where-Object {$_ -match ("Berlin|London|Moscow")}) {
Invoke-Command -ComputerName $server -Credential $cred -ScriptBlock {
#do work
}
}
This method uses the foreach loop and a regex match alternator to look for Berlin, or London, or Moscow, in each line from your list.
UPDATE
To work with a large array of cities, it would be much more readable if you are working with a csv, with headers. Here is an example...
<#
import your server list as a csv
csv looks like this...
server,city,cred
server1,moscow,cred1
server2,london,cred1
server3,moscow,cred2
server4,berlin,cred1
#>
#import serverlist.csv...
$serverlist = Import-csv -path C:\scripts\lists\serverlist.csv
#import your city list...
$citylist = Get-Content -path C:\scripts\lists\citylist.txt
#iterate $serverlist and check if $citylist contains $_.City...
$serverlist | ForEach-Object {
if ($citylist.Contains($_.City)) {
Invoke-Command -ComputerName $_.Name -Credential $_.cred -ScriptBlock {
#do work
}
}
}
UPDATE 2
If you are working with .txt and not .csv, you can also match on the city.
If your serverlist.txt looks like this...
server city cred
server city cred
server city cred
...you can split each row of $serverlist and match on array index values. Powershell will index from 0, so to match on 'city', you would use index [1], etc...
$serverlist = Get-Content -path C:\scripts\lists\serverlist.txt
#import your city list...
$citylist = Get-Content -path C:\scripts\lists\citylist.txt
#iterate $serverlist
#split each row into a collection
#check if $citylist contains a match on index 1 (city 'column')...
$serverlist | ForEach-Object {
$serverInfo = ($_).split(" ")
if ($citylist.Contains($serverInfo[1])) {
Invoke-Command -ComputerName $serverInfo[0] -Credential $serverInfo[2] -ScriptBlock {
#do work
}
}
}

Related

Powershell return variable from within a Invoke-Command

I'm developing a powershell script (and kind of new to it) to go to a couple of servers and extract the RDP logons, so we can check if a certain policy is being followed.
So I've search a bit and now I got the script output exatcly as I want. But now I want to send the result over email.
But I have a problem, because the variable which is output to console (with the info I need) is inside a Invoke-Command, so I cannot use it after outside the Invoke-Command to send the email.
This is my code:
$ServersToCheck = Get-Content "C:\Temp\Servers-RDP2.txt"
foreach ($server in $ServersToCheck) {
Invoke-Command -ComputerName $server -ErrorAction SilentlyContinue {
$username = "user"
$FilterPath = "<QueryList><Query Id='0'><Select Path='Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational'>*[System[(EventID=1149) and TimeCreated[timediff(#SystemTime) <= 604800000]]] and *[UserData[EventXML[#xmlns='Event_NS'][Param1='{0}']]]</Select></Query></QueryList>" -f $username
$RDPAuths = Get-WinEvent -ErrorAction SilentlyContinue -LogName 'Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational' -FilterXPath $FilterPath
[xml[]]$xml = $RDPAuths | Foreach { $_.ToXml() }
$EventData = Foreach ($event in $xml.Event) {
New-Object PSObject -Property #{
TimeCreated = (Get-Date ($event.System.TimeCreated.SystemTime) -Format 'dd-MM-yyyy hh:mm:ss')
User = $event.UserData.EventXML.Param1
Domain = $event.UserData.EventXML.Param2
Client = $event.UserData.EventXML.Param3
Server = hostname
}
}
$EventData | FT
}
}
So, I need to use $EventData outside the Invoke-Command so I can add the results of all servers and then send it over by email.
How can I use that variable outside the Invoke-Command?
Thanks

How to test writing to a file share path using credential?

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

Random string showing up in exported CSV

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.

Modifying Existing CSV file in Powershell

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

Passing variable with properties to argumentlist, loosing properties

$Computers = Get-QADComputer -sizelimit 5
returns a list of five computers. I loop with
foreach($computer in $computers) {
echo "and then I can do this $computer.name"
to get only the computername from $computers.
But When i try to pass it to start-job like this:
Start-Job -FilePath $ScriptFile -Name $Computer.Name -ArgumentList $Computer
I am unable to do a $computer.name inside $scriptfile. I have to pass it like $computer.name and call it like $args[0]. But then I loose all the other properties (I am using a bunch inside $scriptfile.)
What am I not getting here? What would you call $computer? And what would you call $computer.name ?
Sune:)
You should be able to get the Name property with $args[0].Name. If you want to access the name parameter like so: $computer.name, then you need to define a parameter in $ScriptFile:
param(
$Computer
)
$Computer.name
By the way' you can't do this:
echo "and then I can do this $computer.name"
PowerShell expands the value $computer only. Put it in a sub-expression:
echo "and then I can do this $($computer.name)"
Exactly so, this is how I would write something similar:
#Get Services
$Services = Get-Service
#Limit results to the first 5 entries
$Services = $Services[0..4]
#Loop through services
foreach ($Service in $Services)
{
#For each Service run a basic echo to host to prove that the Service was passed in successfully
Start-Job -ScriptBlock { param ($Service) Write-Host "Service Name is $($Service.Name)" } -ArgumentList $Service
}
You can then retrieve the Jobs like this:
#Retrieve Jobs
Get-Job | Receive-Job