Testing and reporting Invoke-command execution - powershell

I have the following PowerShell script that creates a session with Windows server administrator account.I want to report in case of failure of Invoke-command the error and save it in a file
Below is the code that I wrote but if i tamper for example .json file(set a wrong username),execution fails and error_report.txt is not created
#Param(
$user = "lamda"
#)
$user_domain = (Get-WmiObject Win32_ComputerSystem).Domain
$user_computer = (Get-WmiObject Win32_ComputerSystem).Name
$file = "error_report.txt"
If ((Test-Path "creds.json") -eq $True)
{
$jsonfile = Get-ChildItem creds.json
if ($jsonfile.Length -eq 0)
{
#$file = "error_report.txt"
Set-Content -Path $file -Value "Error:The file 'creds.json' is empty"
break
}
else
{
$creds= (Get-Content creds.json | Out-String | ConvertFrom-Json)
$admin = $creds.username
$passwd = $creds.password
if (($admin) -and ($passwd))
{
$Password = ConvertTo-SecureString -String $passwd -AsPlainText -Force
$credential = [pscredential]::new($admin,$Password)
$command = Invoke-Command -ComputerName Server.$user_domain -FilePath
C:\SECnology\Data\Utilities\Updating.ps1 -ArgumentList
$user,$admin,$user_computer -Credential $credential
If ($command -eq $false)
{
$file = "error_report.txt"
Set-Content -Path $file -Value "Error:Session between user and server
could not be created,please check your Credentials"
}
break
}
elseif (([string]::IsNullOrEmpty($admin)) -or ([string
]::IsNullOrEmpty($passwd)))
{
#$file = "error_report.txt"
Set-Content -Path $file -Value "Error:One object of 'creds.json' seems
to be empty.Please check your file "
}
}
break
}
else
{
#$file = "error_report.txt"
Set-Content -Path $file -Value "Error:The file 'creds.json' does not exist"
}

I think the issue is the way you are defining the condition on your if statement.
You use -eq $false however if you connections fails it does not set the vale of command to $false it will leave command as a null as it return no value (it errorred).
What you can try is either use the null operator (!) in your if statement so:
If (!$command){Do stuff}
Or you can give you invoke command an error variable and check if that has a value when run.
$command = Invoke-Command -ComputerName Server.$user_domain -FilePath
C:\SECnology\Data\Utilities\Updating.ps1 -ArgumentList
$user,$admin,$user_computer -Credential $credential -ErrorVariable TheError
If ($TheError)
{Do stuff}

Related

csv powershell cmd output

I use this script for check on multiple machine if I have installed some apps and now the script return in txt file result. (format hostname\nYes/No). How can I change return result in csv file in 2 columns ; 1.Hostname ; 2. Result ?
actual result.txt
hostname
YES / NO
desired csv export
Hostname | Result
hostname | YES / NO / OFFLINE
script
$Computers = Get-Content -Path C:\Users\m\Desktop\ip.txt | Where-Object { $_ -match '\S' }
foreach($Computer in $Computers){
Write-Host $Computer
$User = "ussr"
$Password = "pssd"
$Command = 'hostname && if exist "C:\LdApp.exe" (echo YES) else (echo NO)'
$secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
$Credentials = New-Object System.Management.Automation.PSCredential($User, $secpasswd)
Get-SSHTrustedHost | Remove-SSHTrustedHost
try{
$SessionID = New-SSHSession -ComputerName $Computer -Credential $Credentials -AcceptKey:$true
Invoke-SSHCommand -Index $sessionid.sessionid -Command $Command | Select -Expand Output | Add-Content -Path result.txt}
catch {Add-Content -Path result.txt -Value "$Computer conectare esuata!"}
}
Thank you,
I have modified your code and not tested it. I will do some thing like this
$result = Get-Content -Path C:\Users\m\Desktop\ip.txt | ForEach-Object {
$computer = $_
try {
Write-Host $Computer
$User = "ussr"
$Password = "pssd"
$Command = 'if exist "C:\LdApp.exe" (echo YES) else (echo NO)'
$secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
$Credentials = New-Object System.Management.Automation.PSCredential($User, $secpasswd)
Get-SSHTrustedHost | Remove-SSHTrustedHost
$SessionID = New-SSHSession -ComputerName $Computer -Credential $Credentials -AcceptKey:$true
$output = if ($SessionID) {
(Invoke-SSHCommand -Index $sessionid.sessionid -Command $Command).Output
}
else {
"Offline"
}
}
catch {
{
Write-Host "$Computer conectare esuata!"
$output = "conectare esuata!"
}
}
[PsCustomObject]#{
'Hostname' = $computer
'Status' = $output
}
}
$result | Export-csv -Path "C:\Users\m\Desktop\result.csv" -NoTypeInformation
Answer2: for multiple commands and capturing results, and yet again not tested :(
Get-Content -Path C:\Users\m\Desktop\ip.txt | ForEach-Object {
$computer = $_
try {
Write-Host $Computer
$User = "ussr"
$Password = "pssd"
$Command = 'hostname && if exist "C:\LdApp.exe" (echo YES) else (echo NO)'
$secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
$Credentials = New-Object System.Management.Automation.PSCredential($User, $secpasswd)
Get-SSHTrustedHost | Remove-SSHTrustedHost
$SessionID = New-SSHSession -ComputerName $Computer -Credential $Credentials -AcceptKey:$true
if ($SessionID) {
$Output = (Invoke-SSHCommand -Index $sessionid.sessionid -Command $Command).Output
$result = $Output.split("`n")
[PSCustomObject]#{
"HostName" = $result[0]
"IPAddress" = $result[1] # Print $result to verify the exact index of this value.
"Status" = $result[2]
}
}
else {
[PSCustomObject]#{
"HostName" = $computer
"IPAddress" = "NA"
"Status" = "offline"
}
}
}
catch {
{
Write-Host "$Computer conectare esuata!"
}
}
} | Export-csv -Path "C:\Users\m\Desktop\result.csv" -NoTypeInformation

New-PSDrive "The network path was not found"

i have trouble with "New-PSDrive -Root"
When i try to map a New-PSDrive -Root $patch with a $path using an array, the cmd does not map the drive but give me an error : "The network Path was not found".
The path is working if i use an explorer in my windows.
How can i fix that ?
Thanks a lot
exemple :
foreach ($s in $serverlist)
{
$path = "\\$s\e$\Updates\file\
New-PSDrive -Name "S" -Root $path -Persist -PSProvider "FileSystem" -Credential $cred
}
Problem
This is the entire script :
Get-Date -Format "dddd MM/dd/yyyy HH:mm K">> "C:\file\results.txt"
$cred = Get-Credential -Credential domain\name
$serverlist = #(get-content -Path "C:\file\serverlist.txt")
foreach ($s in $serverlist)
{
$path = "\\$s\e$\Updates\file\"
New-PSDrive -Name "S" -Root $path -Persist -PSProvider "FileSystem" -Credential $cred
$path2 = "\\$s\e$\Updates\file\errors.txt"
$file = Get-Content $path2
$containsWord = $file | %{$_ -match "0"}
if ($containsWord -contains $true) {
Out-File -FilePath "C:\file\results.txt" -Append -InputObject "$s : ok"
} else {
Out-File -FilePath "C:\file\results.txt" -Append -InputObject "$s : nok"
}
Remove-PSDrive -Name "S"
}
EDIT 1 : If i try to access to the file directly by an windows explorer with the same credential and I, after that, run the script, it works
As commented, the user in $cred may have permissions to access the file in the path on the server, but you as it seems do not.
Try using Invoke-Command where you can execute a scriptblock using different credentials than your own:
$cred = Get-Credential -Credential domain\name
$serverlist = Get-Content -Path "C:\file\serverlist.txt"
# loop through the list of servers and have these perform the action in the scriptblock
$result = foreach ($s in $serverlist) {
Invoke-Command -ComputerName $s -Credential $cred -ScriptBlock {
# you're running this on the server itself, so now use the LOCAL path
$msg = if ((Get-Content 'E:\Updates\file\errors.txt' -Raw) -match '0') { 'ok' } else { 'nok' }
# output 'ok' or 'nok'
'{0} : {1}' -f $env:COMPUTERNAME, $msg
}
}
# write to the results.txt file
# change 'Add-Content' in the next line to 'Set-Content' if you want to create a new, blank file
Get-Date -Format "dddd MM/dd/yyyy HH:mm K" | Add-Content -Path 'C:\file\results.txt'
$result | Add-Content -Path 'C:\file\results.txt'
In fact, you don't even need a foreach loop because parameter -ComputerName can receive an array of server names:
$result = Invoke-Command -ComputerName $serverlist -Credential $cred -ScriptBlock {
# you're running this on the server itself, so now use the LOCAL path
$msg = if ((Get-Content 'E:\Updates\file\errors.txt' -Raw) -match '0') { 'ok' } else { 'nok' }
# output 'ok' or 'nok'
'{0} : {1}' -f $env:COMPUTERNAME, $msg
}

Try different credentials PS script SSH

I have this script and cannot work correctly .. I try to connect with 2 users ; if one doesn't work try other one.
#1. Try user and pass1 if is not good try #2. user and pass2.
*problem is with winscp users ; I really don't know how to implement 2 try connection
if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
{
$arguments = "& '" +$myinvocation.mycommand.definition + "'"
Start-Process powershell -Verb runAs -ArgumentList $arguments
Break
}
Add-Type -Path "C:\Program Files (x86)\WinSCP\WinSCPnet.dll"
$db = import-csv -Path "C:\Program Files (x86)\WinSCP\db.csv"
$inputID = Read-Host -Prompt "ID"
$entry = $db | where-Object {$_.HostName -eq $inputID}
if ($inputID -eq $entry.HostName){
"$inputID Ok!"
}
else{
"$inputID nu exista in baza de date!"
$title = 'Title'
$question = 'Doriti sa introduceti un ID nou in Baza de Date?'
$choices = '&Yes', '&No'
$decision = $Host.UI.PromptForChoice($title, $question, $choices, 1)
if ($decision -eq 0) {
Write-Host 'confirmed'
$ID = Read-Host -Prompt "Introduceti ID"
$IP = Read-Host -Prompt "Introduceti IP"
$wrapper = New-Object PSObject -Property #{ HostName = $ID; IP = $IP }
Export-Csv -Append -InputObject $wrapper -Path "C:\Program Files (x86)\WinSCP\db.csv" -NoTypeInformation -Force
$dbTrimmer = Get-Content -Path "C:\Program Files (x86)\WinSCP\db.csv"
$dbTrimmer.Replace('","',",").TrimStart('"').TrimEnd('"') | Out-File "C:\Program Files (x86)\WinSCP\db.csv" -Force -Confirm:$false
Exit
}
else{
Write-Host 'No'
Exit
}
}
Write-Host "IP:" $entry.IP
$User = "user"
$Password = "pass"
$Command = "C:\Info.exe"
$secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
$Credentials = New-Object System.Management.Automation.PSCredential($User, $secpasswd)
Get-SSHTrustedHost | Remove-SSHTrustedHost
$SessionID = New-SSHSession -ComputerName $entry.IP -Credential $Credentials -AcceptKey:$true
Invoke-SSHCommand -Index $sessionid.sessionid -Command $Command
# Set up session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property #{
Protocol = [WinSCP.Protocol]::Sftp
HostName = $entry.IP
UserName = "$User"
Password = "$Password"
GiveUpSecurityAndAcceptAnySshHostKey = "true"
}
$session = New-Object WinSCP.Session
$file = "Dev.log", "Info.dat"
$localPath = "E:\Arhive\*"
$remotePath = "/C:/Program Files/Dev.log", "/C:/Program File/Info.dat"
try {
# Connect
$session.Open($sessionOptions)
# Check exists files
foreach ($remotePath in $remotePath)
{
if ($session.FileExists($remotePath))
{
Write-Host "Fisierul $remotePath exista"
# Transfer files
$session.GetFiles($remotePath, $localPath).Check()
}
else
{
Write-Host "Fisierul $remotePath NU exista"
}
}
}
finally {
$session.Dispose()
}
foreach ($file in "E:\loguri\Dev.log", "E:\loguri\Info.dat") {
if (Test-Path $file) {
Compress-Archive $file -DestinationPath "E:\Arhive\$inputID.zip" -Update
Remove-Item $file
}
}
# Stergere fisiere din Arhive mai vechi de 60 minute
$Files = get-childitem 'E:\Arhive' | Where-Object PSIsContainer -eq $false
$LimitTime = (Get-Date).AddMinutes(-60)
$Files | ForEach-Object {
if ($_.CreationTime -lt $LimitTime -and $_.LastWriteTime -lt $LimitTime) {
Remove-Item -Path $_.FullName -Force
Write-Host "Am sters $Files pentru ca sunt mai vechi de $LimitTime !"
}
}
Here is all my script. In this moment all works very well , just I want to add 2 users for auth. If 1 fail try other one.
Any ideea ? Thank you
I couldn't test this myself, but I think I would go about it like below:
$User = "SameUser"
$Password = "Pass1"
$sPassword = "Pass2"
$Command = "C:\Info.exe"
$secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
$ssecpasswd = ConvertTo-SecureString $sPassword -AsPlainText -Force
Get-SSHTrustedHost | Remove-SSHTrustedHost
try {
# try the first credentials
$Credentials = New-Object System.Management.Automation.PSCredential($User, $secpasswd)
$SessionID = New-SSHSession -ComputerName $entry.IP -Credential $Credentials -AcceptKey:$true -Verbose -ErrorAction Stop
}
catch {
# first one failed, try second credentials
$Credentials = New-Object System.Management.Automation.PSCredential($User, $ssecpasswd)
$SessionID = New-SSHSession -ComputerName $entry.IP -Credential $sCredentials -AcceptKey:$true -Verbose
}
try {
Invoke-SSHCommand -SessionId $SessionID.SessionId -Command $Command -ErrorAction Stop
}
catch {
throw
}
# create a hashtable with the first password
$options = #{
Protocol = [WinSCP.Protocol]::Sftp
HostName = $entry.IP
UserName = $User
Password = $Password
GiveUpSecurityAndAcceptAnySshHostKey = $true
}
try {
# Set up session options using first password
$sessionOptions = New-Object WinSCP.SessionOptions -Property $options
$session = New-Object WinSCP.Session
# Try Connect
$session.Open($sessionOptions)
}
catch {
# Set up session options using second password
$options['Password'] = $sPassword
try {
$sessionOptions = New-Object WinSCP.SessionOptions -Property $options
$session = New-Object WinSCP.Session
# Try Connect
$session.Open($sessionOptions)
}
catch {
Write-Error "Could not open WinSCP session: $($_.Exception.Message)"
throw
}
}
try {
# Check if exists files.
# Make sure variables $remotePath and $localPath are defined on top of the script
foreach ($remoteFile in $remotePath) {
if ($session.FileExists($remoteFile)) {
$session.GetFiles($remotePath, $localPath).Check()
}
else {
Write-Warning "File '$remoteFile' not found"
}
}
}
catch {
Write-Error "Could not open WinSCP session: $($_.Exception.Message)"
}
finally {
if ($session) { $session.Dispose() }
}

Remotely registry path controlling via powershell

My conditions are :
if NeedReboot key is exist then result will be Pending reboot
if NeedReboot key is not exist then result will be NO Pending reboot
My desired output :
Computername,Rebootrequired
Host01,Pending reboot
Host02,NO Pending reboot
Script :
$allComputers = Get-Content '.\path\to\computers.txt'
$domainCred = Get-Credential -UserName "domain01\admin01" -Message "Please enter the DOMAIN password"
$Results = foreach($computer in $allComputers) {
$cred = $domainCred
try {
$VmTools = Invoke-Command -ComputerName $computer -Credential $cred -ScriptBlock {
Test-Path -Path "HKLM:\SOFTWARE\VMware, Inc.\NeedReboot\"
} -ErrorAction Stop
[PsCustomObject]#{ ComputerName = $computer; RebootRequired = ([int]$VmTools -eq "True") }
}
}
$Results | Format-Table -AutoSize
$Results | Export-Csv -Path 'c:\temp\vmtools.csv' -NoTypeInformation -UseCulture
Try removing the final backslash after the registry path.
Then, Test-Path just returns $true or $false, so converting that to [int] and comparing to the string "True" makes no sense..
Try
[PsCustomObject]#{
ComputerName = $computer
RebootRequired = if($VmTools) {"Pending Reboot") } else {"NO Pending Reboot"}
}
The code is also missing a catch{..} block that should accompany the try {..} block. Something like:
catch {
[PsCustomObject]#{
ComputerName = $computer
RebootRequired = "Error reading registry"
}
}

Powershell export all detailed logging to csv file

I have inherited a script that is not working. I need to capture everything that would normally output to the console, including Success and Error entries from the script. This is only a small portion of the script, and it only captures errors. Any help would be appreciated on getting all output to the file instead of the console.
An example is the Write-Verbose "VERIFYING contact for $($User.WindowsEmailAddress)"
I know this is writing to the console, but I need it to write to the log that is defined at the very bottom of the script.
Catch
{Out-File -InputObject "$(Get-Date -Format MM.dd.yyyy-HH:mm:ss);$($WriteMode);ERROR;Target;$($targetUser.Split('#')[1]);$($User.WindowsEmailAddress);Update;;;Error updating user: $($Error[0])" -FilePath $LogFilePath -Append}
I hope this makes sense.
### UPDATES
ForEach ($User in $colUpdContact)
{
Write-Verbose "VERIFYING contact for $($User.WindowsEmailAddress)"
#Filter used to find the target contact object(s)
$strFilter = "WindowsEmailAddress -eq `"$($User.WindowsEmailAddress)`""
Try
{$colContacts2 = Invoke-Command -Session $targetSession -ScriptBlock {param ($strFilter) Get-Contact -Filter $strFilter} -ArgumentList $strFilter -ErrorAction Stop}
Catch
{Out-File -InputObject "$(Get-Date -Format MM.dd.yyyy-HH:mm:ss);$($WriteMode);ERROR;Target;$($targetUser.Split('#')[1]);$($User.WindowsEmailAddress);Find;;;Error getting contact: $($Error[0])" -FilePath $LogFilePath -Append}
ForEach ($Contact in $colContacts2)
{
#initialize update string and cmd string
$strUpdateContact = $null
$updateCmd = $null
$strWriteBack = $null
$writeBackCmd = $null
#Iterate through attributes and append to the strUpdateContact string if the attribute value has changed
ForEach ($Attrib in $arrAttribs)
{
If ($User.$Attrib -ne $Contact.$Attrib)
{
if($ReadOnly){
Add-Content -Path $readOnlyFilePath -Value " Changing $Attrib"
Add-Content -Path $readOnlyFilePath -Value " Before: $($Contact.$Attrib)"
Add-Content -Path $readOnlyFilePath -Value " After: $($User.$Attrib)"
}
$strUpdateContact += " -$($Attrib) `"$($User.$Attrib)`""
Out-File -InputObject "$(Get-Date -Format MM.dd.yyyy-HH:mm:ss);$($WriteMode);CHANGE;Target;$($targetUser.Split('#')[1]);$($User.WindowsEmailAddress);Update;$($Contact.$Attrib);$($User.$Attrib);" -FilePath $LogFilePath -Append
}
}
#Check if LegacyExchangeDN has been written back to User object
$mailContact = Invoke-Command -Session $targetSession -ScriptBlock {param ($contact) Get-MailContact $($contact.WindowsEmailAddress)} -ArgumentList $Contact -ErrorAction Stop
$x500 = "X500:$($mailContact.LegacyExchangeDN)"
$userRec = Invoke-Command -Session $sourceSession -ScriptBlock {param ($User) Get-Recipient $($User.WindowsEmailAddress)} -ArgumentList $User -ErrorAction Stop
if($UserRec.emailAddresses -notcontains $x500){
$userName = ($user.UserPrincipalName).Split('#')[0]
if($userName -eq "")
{
$userName = $user.SamAccountName
}
$strWriteBack = "Set-ADUser -Identity $userName -Add #{ProxyAddresses=`"$x500`"} -Server $sourceDC -Credential `$sourceDCCredential"
}
#If there is anything to update
If ($strUpdateContact.Length -gt 0)
{
Write-Verbose "Updating attributes for $($User.WindowsEmailAddress)"
#Prepend the command for the contact being modified
$strUpdateContact = "Set-Contact $($User.WindowsEmailAddress) " + $strUpdateContact
If ($ReadOnly)
{Add-Content -Path $readOnlyFilePath -Value $strUpdateContact}
Else
{
Try
{
#Create the complete command and invoke it
$updateCmd = "Invoke-Command -Session `$targetSession -ScriptBlock {$($strUpdateContact)}"
Invoke-Expression $updateCmd -ErrorAction Stop
}
Catch
{Out-File -InputObject "$(Get-Date -Format MM.dd.yyyy-HH:mm:ss);$($WriteMode);ERROR;Target;$($targetUser.Split('#')[1]);$($User.WindowsEmailAddress);Update;;;Error updating contact: $($Error[0])" -FilePath $LogFilePath -Append}
}
}
If ($strWriteBack){
Write-Verbose "Updating X500 for $($User.WindowsEmailAddress)"
Out-File -InputObject "$(Get-Date -Format MM.dd.yyyy-HH:mm:ss);$($WriteMode);CHANGE;Target;$($targetUser.Split('#')[1]);$($User.WindowsEmailAddress);Update;;$x500;" -FilePath $LogFilePath -Append
If($ReadOnly){
Add-Content -Path $readOnlyFilePath -Value $strWriteBack
}
else{
Try
{
Invoke-Expression $strWriteBack -ErrorAction Stop
}
Catch
{Out-File -InputObject "$(Get-Date -Format MM.dd.yyyy-HH:mm:ss);$($WriteMode);ERROR;Target;$($targetUser.Split('#')[1]);$($User.WindowsEmailAddress);Update;;;Error updating user: $($Error[0])" -FilePath $LogFilePath -Append}
}
}
}
}
Why you not use the Start-Transcript to output all the information into a log file, and then you can manually copy anything you want?
An example for the command:
Start-Transcript -Path $TranscriptOutputFile -Append -Force
#Your script; write-output 'something update';
Stop-Transcript
Everything output by write-output command will be appended into the log file.