Unable to re-write the file in powershell using Out-File - powershell

I am trying to write a powershell script to write the output of a command to a file. It runs perfectly fine for the first time. When i re-run the script it comes with an error
Out-File : The process cannot access the file '//Filepath' because it is
being used by another process.
+ (Invoke-Command -Session $s -ScriptBlock $command )| Out-File
$file -For ...
Code block :
$command = {
$x = (Get-Date).AddMinutes(-5).ToShortTimeString() ;
$y = "{0:HH:mm}" -f [datetime] $x ;
cd $path ;
dumplog ctisvr /bt $y /m "CSTAUniversalFailureConfEvent"
}
#$s = New-PSSession -ComputerName
(Invoke-Command -Session $s -ScriptBlock $command )| Out-File $file -Force

My suggestion would be that this is not PoSH cause issue but the dumplog command you are using. There are programs with will not lock files and those that will. I've never used dumplog, I can only speculate here.
So, it would be best to check for the process state of the exe and file before doing anything with it afterwards.
Like simply looking for a dumplog process after the first serialization is done, the kill any dumplog process before trying again.
If this is on a file share you can use...
Get-SmbOpenFile
Or
openfiles
If it is local you can do something like...
What file is open by a given program
Get-CimInstance Win32_Process -Filter "name = 'dumplog.exe'" | Format-List -Property *
Get the owner of the process
$ProcessId = Get-CimInstance Win32_Process -Filter "name = 'dumplog.exe'"
Invoke-CimMethod -InputObject $ProcessId -MethodName GetOwner
What process has a file locked or filter the results based on the file name:
($ProcessId = Get-CimInstance Win32_Process | where commandline -match 'CSTAUniversalFailureConfEvent')
Kill the process
Invoke-CimMethod -InputObject $ProcessId -MethodName Terminate

Related

Using Powershell Get-ItemProperty through all of AD computers object

I'm a complete newbie in Powershell (and programming as you may have guessed), I want to get the result of the following PS command for each of our AD computer object and print the result in a text file...but I'm completely lost. Does anyone have a lifeline I could hold on to?
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" | Select-String -Pattern "mysoftwarename"
Thank you very much.
$ScriptBlock = {Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" | Select-String -Pattern "mysoftwarename"}
$Computers = (Get-ADComputers -filter * ).name
$Creds = (Get-Credential)
foreach ($Computer in $Computers)
{
"`n`n$Computer`n" >> .\file.txt # "`n" just emulates Enter key press
Invoke-Command -ComputerName $Computer -ScriptBlock $ScriptBlock -Credential $Creds >> .\file.txt
}
This will work fine if you have all your computers online and PS remoting configured properly. Otherwise, it will require modifications.

PowerShell Jobs vs Start-Process

I made a little diagnostic script that I keep in my $profile. In collecting the CPU name I found that the command takes about 4 seconds (Get-WmiObject -Class Win32_Processor).Name. So I thought I'd try PowerShell Jobs and while I think they will be really good for long background jobs, if you just want to quickly grab a small piece of information in the background, the initialisation times are awkward (like 2-3 sec per job) so I thought I'd use Start-Process to dump values in temp files while the rest of my script runs. I think I'm doing this correctly, but if you run this function 3 or 4 times, you'll notice that CPU name is not populated.
• Is using Start-Process like this optimal, or does anyone have a quicker way to just start small jobs in the background in parallel? I know there is a .NET way of doing this (but it seems super-complex from what I've seen)?
• Do you know why my "wait for file to be created and be non-zero before accessing it" is failing so regularly?
function sys {
$System = get-wmiobject -class "Win32_ComputerSystem"
$Mem = [math]::Ceiling($System.TotalPhysicalMemory / 1024 / 1024 / 1024)
$wmi = gwmi -class Win32_OperatingSystem -computer "."
$LBTime = $wmi.ConvertToDateTime($wmi.Lastbootuptime)
[TimeSpan]$uptime = New-TimeSpan $LBTime $(get-date)
$s = "" ; if ($uptime.Days -ne 1) {$s = "s"}
$uptime_string = "$($uptime.days) day$s $($uptime.hours) hr $($uptime.minutes) min $($uptime.seconds) sec"
$temp_cpu = "$($env:TEMP)\ps_temp_cpu.txt"
$temp_cpu_cores = "$($env:TEMP)\ps_temp_cpu_cores.txt"
$temp_cpu_logical = "$($env:TEMP)\ps_temp_cpu_logical.txt"
rm -force $temp_cpu -EA silent ; rm -force $temp_cpu_cores -EA silent ; rm -force $temp_cpu_logical -EA silent
Start-Process -NoNewWindow -FilePath "powershell.exe" -ArgumentList "-NoLogo -NoProfile (Get-WmiObject -Class Win32_Processor).Name > $temp_cpu"
Start-Process -NoNewWindow -FilePath "powershell.exe" -ArgumentList "-NoLogo -NoProfile (Get-WmiObject -Class Win32_Processor).NumberOfCores > $temp_cpu_cores"
Start-Process -NoNewWindow -FilePath "powershell.exe" -ArgumentList "-NoLogo -NoProfile (Get-WmiObject -Class Win32_Processor).NumberOfLogicalProcessors > $temp_cpu_logical"
""
"Hostname: $($System.Name)"
"Domain: $($System.Domain)"
"PrimaryOwner: $($System.PrimaryOwnerName)"
"Make/Model: $($System.Manufacturer) ($($System.Model))" # "ComputerModel: $((Get-WmiObject -Class:Win32_ComputerSystem).Model)"
"SerialNumber: $((Get-WmiObject -Class:Win32_BIOS).SerialNumber)"
"PowerShell: $($PSVersionTable.PSVersion)"
"Windows Version: $($PSVersionTable.BuildVersion)"
"Windows ReleaseId: $((Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'ReleaseId').ReleaseId)"
"Display Card: $((Get-WmiObject -Class:Win32_VideoController).Name)"
"Display Driver: $((Get-WmiObject -Class:Win32_VideoController).DriverVersion)"
"Display ModelDesc: $((Get-WmiObject -Class:Win32_VideoController).VideoModeDescription)"
"Last Boot Time: $([Management.ManagementDateTimeConverter]::ToDateTime((Get-WmiObject Win32_OperatingSystem | select 'LastBootUpTime').LastBootUpTime))" # $(wmic OS get LastBootupTime)
"Uptime: $uptime_string"
# ipconfig | sls IPv4
Get-Netipaddress | where AddressFamily -eq IPv4 | select IPAddress,InterfaceIndex,InterfaceAlias | sort InterfaceIndex
# Get-PSDrive | sort -Descending Free | Format-Table
# https://stackoverflow.com/questions/37154375/display-disk-size-and-freespace-in-gb
# https://www.petri.com/checking-system-drive-free-space-with-wmi-and-powershell
# https://www.oxfordsbsguy.com/2017/02/08/powershell-how-to-check-for-drives-with-less-than-10gb-of-free-diskspace/
# Get-Volume | Where-Object {($_.SizeRemaining -lt 10000000000) -and ($_.DriveType -eq “FIXED”) -and ($_.FileSystemLabel -ne “System Reserved”)}
gwmi win32_logicaldisk | Format-Table DeviceId, VolumeName, #{n="Size(GB)";e={[math]::Round($_.Size/1GB,2)}},#{n="Free(GB)";e={[math]::Round($_.FreeSpace/1GB,2)}}
# Note: -EA silent on Get-Item otherwise get an error
while (!(Test-Path $temp_cpu)) { while ((Get-Item $temp_cpu -EA silent).length -eq 0kb) { Start-Sleep -Milliseconds 500 } }
"CPU: $(cat $temp_cpu)"
while (!(Test-Path $temp_cpu_cores)) { while ((Get-Item $temp_cpu_cores -EA silent).length -eq 0kb) { Start-Sleep -Milliseconds 500 } }
"CPU Cores: $(cat $temp_cpu_cores)"
while (!(Test-Path $temp_cpu_logical)) { while ((Get-Item $temp_cpu_logical -EA silent).length -eq 0kb) { Start-Sleep -Milliseconds 500 } }
"CPU Logical: $(cat $temp_cpu_logical)"
rm -force $temp_cpu -EA silent ; rm -force $temp_cpu_cores -EA silent ; rm -force $temp_cpu_logical -EA silent
"Memory: $(Get-CimInstance Win32_PhysicalMemory | Measure-Object -Property capacity -Sum | Foreach {"{0:N2}" -f ([math]::round(($_.Sum / 1GB),2))}) GB"
""
"Also note the 'Get-ComputerInfo' Cmdlet (has more info but slower to run)"
""
}
To run jobs in background in powershell, there are these 3 ways to go about it
1. Invoke-Command[3] -scriptblock { script } -asJob -computername localhost
2. Start-Job[2] -scriptblock { script }
3. Start-Process[1] powershell {script}
If you truly want to run things in the background with each job being independent of each other, you'll have to think about using the first or second option as neither of them require the output to be written to a file.
Invoke-Command starts a new session with the system and runs the job in a new instance.
Start-Job creates a new job in the background under a new powershell instance, takes a little more time to allocate the resources and start the process. Just like start-process, Start-Job will run the job in a separate powershell.exe instance.
Start-Process requires you to redirect the standard output to a file[1]. You have to rely on the performance of the disk and how fast your reads and writes are. You also have to ensure that no more than one thread is reading/writing to the output of this process.
Recommendation
I found Invoke-Command to be the fastest when running 100 concurrent jobs to get the processor info. This option does require you to provide -ComputerName which then requires you to be an admin to start a winrm Session with localhost. If you dont output the job information while creating the jobs, it does not take away any significant time.
Start-Job and Invoke-Command both took about a second to get the processor info and running 100 concurrent jobs to get the same thing took some overhead.
$x = 0..100 | Invoke-Command -computername localhost -scriptblock { script } -asJob
$x | % { $_ | wait-job | out-null }
$output = $x | % { $_ | Receive-Job}
# You can run measure-object, sort-object, etc as well
[1]Start-Process
RedirectStandardOutput: Specifies a file. This cmdlet sends the output generated by the process to a file that you specify. Enter the path and filename. By default, the output is displayed in the console.
[2]Start-Job
The Start-Job cmdlet starts a PowerShell background job on the local computer. ... A PowerShell background job runs a command without interacting with the current session.
[3]Invoke-Command
The Invoke-Command cmdlet runs commands on a local or remote computer and returns all output from the commands, including errors. ... To run a command in a background job, use the AsJob parameter

Two Invoke-Command at the same time

I'm using this PowerShell script to resume Bitlocker on every active device:
Get-Content "clients.txt" | ForEach-Object {
if (Test-Connection $_ -Count 1 -ErrorAction 0 -Quiet) {
Invoke-Command -ComputerName $_ -ScriptBlock {
Resume-BitLocker -MountPoint "C:"
}
} else {
Write-Host "$_ is OFFLINE" -ForegroundColor Red
}
}
But I also want to trigger a hardware inventory via Invoke-WMIMethod on every active device with this command:
Invoke-WMIMethod -ComputerName $Server -Namespace root\ccm -Class SMS_CLIENT -Name TriggerSchedule "{00000000-0000-0000-0000-000000000001}"
I was able to script the first part but it isn't that well to built in the second command.
you are drifting a bit in the wrong direction.
When using Invoke-Command, it processes the scriptblock, against 32 computers simultaneously (in parallel)!
If you are processing computers with foreach, it would handle them sequentially, which would be much slower.
Same is valid when using *WMI cmdlets. Always try to replace them with the corresponding CIM cmdlets, as the same logic applies - computers are being processed in parallel.
Consider something like:
$ComputerList = Get-Content -Path Clients.txt
Invoke-Command -ComputerName $ComputerList -ErrorAction SilentlyContinue -ScriptBlock {
Resume-BitLocker -MountPoint "C:"
#Add second command
#add third command and so on
}
I am not sure, what would be the alternative command to Invoke-WMIMethod, when executing locally. Maybe Set-WMIInstance, but I am only speculating!
Then if you would like to add second command for execution, just add it into the scriptblock of Invoke-Command.

Kill only a specific Command Prompt Process Remotely with PowerShell

I am trying to write a script to remote kill a specific command prompt process.
If I locally run get-process I can see that the CMD.exe process can be narrowed down by what is set in the field "MainWindowTitle"
If I get use Get-Process -computer name or get CIMInstance the field "MainTitleWindow" comes back as blank.
$ses = New-CimSession -ComputerName $computer -Credential $cred
$process = Get-CimInstance -ClassName CIM_process -CimSession $ses -filter "name = 'cmd.exe'"
$process | Select-Object name,MainWindowTitle
Remove-CimSession -CimSession $ses
name MainWindowTitle
---- ---------------
cmd.exe
Extracted from MSDN:
A process has a main window associated with it only if the process has
a graphical interface. If the associated process does not have a main
window (so that MainWindowHandle is zero), MainWindowTitle is an empty
string ("").
More info here.
I tried to compare with other processes but the result is the same...
You could parse the output of the tasklist command. Like this:
$cmds = tasklist /v |
Where-Object {$_ -like "cmd.exe*"} |
ForEach-Object {
New-Object -TypeName PSObject -Prop #{"ID"=[int]$_.Substring(30,5); `
"Title"=$_.Substring(157)}
}
$cmds
$Process = Get-CimInstance CIM_Process -CimSession $Ses | Where-Object{ $ProcessNames -contains $_.Name }
using this you can also get the ProcessID and ParentProcessID.
you can then use the similar commands to then find the CMD process that uses the ParentProcessID of your main script.
You must close both the main process of the script and the command prompt as well or else the script will keep running.

How to run script against windows servers WinRM

I am trying to run a script that searches/downloads/installs windows updates on remote computers using WinRM. I am running this script as a domain user with Admin access. However, I get an ACCESS Denied error.
Now, I have the script copied over to the remote servers but I am unable to view output to see whether the script is running or not.
OUTPUT I want to see:
# Continue running on other servers on error
$ErrorActionPreference = "Continue"
# Server list
$servers = Get-Content "C:\Users\admin\Desktop\vm-nonprod.txt"
# Logs
$log = "C:\Users\admin\Desktop\log-nonprod.txt"
# Path to script on server list
$scriptpath = "C:\Patch.ps1"
$results = #()
foreach ($server in $servers) {
try {
$Credential = Import-CliXml -Path "C:\Users\admin\Desktop\admin.Cred"
#New-PSSession -ComputerName $server -Credential $Credential
Invoke-Command -ComputerName $server -Credential $Credential -ScriptBlock {$scriptpath} -ArgumentList "Y" | Out-File -FilePath C:\Users\admin\Desktop\WinPatch.txt
#Invoke-Command -ComputerName $server -Credential hhq\admin -FilePath "C:\Users\admin\Documents\Patch.ps1"
#Copy-Item -Path C:\Users\admin\Documents\Patch.ps1 -Destination 'C:\' -ToSession (New-PSSession –ComputerName $server -Credential $Credential)
}
catch {
Write-Output ("Error running script on remote host: " + $server)
}
}
$results | Export-Csv -NoTypeInformation $log
There's a few issues here.
Does the script exist on the server?
Sounds like yes, you have Patch.ps1 in C:\ on each $server
The scriptblock does not run the script - just prints the variable.
To run it, change {$scriptpath} to {. $scriptpath} or {& $scriptpath}
The variable $scriptpath is not in the scriptblock scope - you will have to pass it in the -ArgumentList
Change: {$scriptpath} -ArgumentList "Y"
____To: {param($p); . $p} -ArgumentList $scriptpath
The argument "Y" is being passed to the scriptbock, not the script. The scriptblock is not looking for it, so this value is being lost.
Assume you want it to be passed to the script - this needs to be done in the scriptblock:
{$scriptpath "Y"}
I would recommend getting rid of Out-File until you are happy with the output in the console.
Putting it all together:
-ScriptBlock {$scriptpath} -ArgumentList "Y" | Out-File -FilePath C:\Users\admin\Desktop\WinPatch.txt
-ScriptBlock {param($p); . $p "Y"} -ArgumentList $scriptpath
I believe you have the wrong Invoke-Command commented out. The one that is running only has the user name hhq\admin in the credential parameter. It might be failing due to that because it would be prompting for the password during run-time.