title: PS1 Replace or Remove a single character without rewriting the whole file
body: <p><strong>Is it possible to remove or replace the last character on the last non-whitespace line of a file using PowerShell 1?</strong></p>
  
  <p><strong>I'm trying to get an Uptime log that is precise to within 5 minutes.</strong></p>
  
  <p>I've found that there are built logs and commands that can be accessed through command prompt that would tell me when the last time a computer was booted up, or when it shut down correctly, but the native uptime log only records once every 24 hrs, so if there is a power failure, I won't know how long the system has been offline with any precision more refined than 24 hours.</p>
  
  <p>So I have created the following script:</p>
  
  <pre><code>$bootTime = (Get-WmiObject Win32_OperatingSystem).LastBootUpTime
  $formBootTime = [Management.ManagementDateTimeConverter]::ToDateTime($bootTime)
  $uptime = (Get-Date)-$formBootTime
  "$formBootTime,$(Get-Date),{0:00},{1:00},{2:00},{3:00}" -f $uptime.Days,$uptime.Hours,$uptime.Minutes,$uptime.Seconds &gt;&gt; C:\UptimeTracker.csv
  </code></pre>
  
  <p>However, this gets tediously long to scroll through when I want to evaluate how long my machine has been running over the last X days.</p>
  
  <p>So I thought I would add a marker to identify the current or most recent Uptime log per any given Boot.</p>
  
  <p><b>But in order for that to work I would need to be able to remove said marker as soon as the previous record is no longer the relevant record.</b></p>
  
  <pre><code>$bootTime = (Get-WmiObject Win32_OperatingSystem).LastBootUpTime
  $formBootTime = [Management.ManagementDateTimeConverter]::ToDateTime($bootTime)
  $file = (Get-Content c:\UptimeTracker.csv | Measure-Object)
  $numberOfLines = $file.Count
  $numberOfWords = (Get-Content c:\UptimeTracker.csv | Select -Index ($numberOfLines -1) | Measure-Object -word)
  $Line = Get-Content c:\UptimeTracker.csv | Select -Index ($numberOfLines -2)
  $wordArray = $Line.Split(",")
  $LastLineBT = $wordArray[0]
  if($LastLineBT -eq $formBootTime) {
      $unmark = "true"
  }
  else
  {$unmark = "false"}
  
  if($unmark == "true"){ &lt;remove last character of file&gt; }
  
  $uptime = (Get-Date)-$formBootTime 
  "$formBootTime,$(Get-Date),{0:00},{1:00},{2:00},{3:00},X" -f $uptime.Days,$uptime.Hours,$uptime.Minutes, $uptime.Seconds &gt;&gt; C:\UptimeTracker.csv
  </code></pre>
  
  <p>Some of the above is borrowed and modified from: <a href="https://stackoverflow.com/a/16210970/11035837">https://stackoverflow.com/a/16210970/11035837</a></p>
  
  <p>I have seen several methods that receive the file as the input file and write to a different output file, and from there it would be an easy thing to do to script renaming the new and old files to switch their positions (new, old, standby - and rotate) the reason I'm trying not to rewrite the whole file is to reduce those instances where the command/script is interrupted and the action doesn't complete.  Ideally the only time the action doesn't complete would be on a power failure. However, I have already seen in a previous version, it would skip 5 minute intervals occasionally for up to 15 minutes without any change in the last reported boot time.  <b>I suspect this has to do with other higher priority processes preventing the task scheduler from running the script.</b>  If this is the case, then a complete rewrite of the file failing part way through the script would lose some percentage of the existing log data, and <b>I would rather miss the latest record than all the data.</b></p>
  
  <p><b>Nothing I have found indicates any ability to remove/replace the last character (or two since one is a newline char), neither have I found anything that explicitly declares this is not possible - I have found declarations that it is not possible to elective replace inner or beginning content without a complete rewrite.</b></p>
  
  <p>Barring any solution definitive answer, or if the definitive answer is no this cannot be done, then I will attempt something like the following:</p>
  
  <pre><code>if($unmark == "true"){     
      $input = "C:\UptimeTracker_CUR.csv"
      $output = "C:\UptimeTracker_NEW.csv"
  
      $content = Get-Content $input
      $content[-2] = $content[-2] -replace 'X', ' '
      $content | Set-Content $output
      Rename-Item -Path "C:\UptimeTracker_CUR.csv" -NewName "C:\UptimeTracker_SBY.csv"
      Rename-Item -Path "C:\UptimeTracker_NEW.csv" -NewName "C:\UptimeTracker_CUR.csv"
  }
  </code></pre>
  
  <p><strong>EDIT - due to multi-read comment by TheMadTechnician</strong></p>
  
  <pre><code>...
  $file = Get-Content c:\UptimeTracker.csv
  $fileMeasure = ($file | Measure-Object)
  $numberOfLines = $fileMeasure.Count
  $numberOfWords = ($file | Select -Index ($numberOfLines -1) | Measure-Object -word)
  $Line = $file | Select -Index ($numberOfLines -2)
  ...
  ...
  if($unmark == "true"){     
      $output = "C:\UptimeTracker_NEW.csv"
  
      $file[-2] = $file[-2] -replace 'X', ' '
      $file | Set-Content $output
      Rename-Item -Path "C:\UptimeTracker.csv" -NewName "C:\UptimeTracker_SBY.csv"
      Rename-Item -Path "C:\UptimeTracker_NEW.csv" -NewName "C:\UptimeTracker.csv"
  }
  </code></pre>