Powershell script - Transcript - How I disable it

1k Views Asked by At

I have got powershell script. Over scheduler I run bat file which run PS1 file.

BAT file

Powershell.exe -executionpolicy remotesigned -File script.PS1

PS1 file

$path = "D:\data";
$limit = (Get-Date).AddHours(-3);
Get-ChildItem -Path $path -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } | Remove-Item -Force;

In the same directory the powershell creates directories in format YYYYMMDD with files PowerShell_transcript.SERVER.t7eh07Gy.20230914031500.txt. Here is the entire script run.

For example: D:\data\20230914\PowerShell_transcript.SERVER.t7eh07Gy.20230914031500.txt D:\data\20230915\PowerShell_transcript.SERVER.t7eh07Gy.20230914031500.txt etc.

How do I disable it?

I have tried stop-transcript, and etc.

I need to stop transcript.

2

There are 2 best solutions below

2
Re1ter On BEST ANSWER

The start of transcription can be determined by group policy and executed independently of your script. Check the policy for example here:

HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription

If there is a EnableTranscripting value in this key and it isn't equal to zero, then transcription is enabled.

0
mklement0 On

Re1ter's helpful answer has provided the crucial pointer; let me add some background information:

  • What you're seeing are the effects of an enabled Turn on PowerShell Transcription GPO (group-policy [object]), which results in automatic transcriptions of all PowerShell sessions (whether interactive or not), as if you had called Start-Transcript and Stop-Transcript at the start and end of each session).

  • If this policy is enabled:

    • The transcripts are stored inside calendar-day-specific directories using the name format yyyMMdd, e.g. 20230918 on 18 Sept. 2023, in files using the name format PowerShell_transcript.*.*.*.txt, where the * components represent the following, in order:

      • the computer name (e.g. server1)
      • a random string of 8 alphanumeric characters (e.g. IjrFVsl1)
      • a sortable local timestamp; the equivalent of [datetime]::Now.ToString('s') -replace '\D' at the start of the session (e.g, ``)
      • Example:
        • PowerShell_transcript.server1.IjrFVsl1.20230918162121.txt
    • The calendar-day-specific directories are stored in the user's Documents directory by default (typically, $HOME\Documents; determine with [System.Environment]::GetFolderPath('MyDocuments')), but the location is configurable.

  • The policy settings are stored in the registry, where they can be queried, but they shouldn't be modified there (which you could technically only do as an administrator): registry modifications aren't synced with the originating GPOs and, in domain environments, may even get overwritten on every reboot / logon.

    • Instead, the policies should be managed either via the gpedit.msc GUI or the cmdlets in the GroupPolicy PowerShell modules.
  • The policy can be defined either at the machine level, stored in the HKEY_LOCAL_MACHINE (HKLM:) registry hive, or at the user level, stored in each user's HKEY_CURRENT_USER (HKCU:) registry hive.

    • If policies are defined at both levels (which doesn't make sense), the machine-level policy takes precedence.
  • PowerShell (Core) 7+ has separate policies from Windows PowerShell, although it can be configured to delegate to the latter's policies.


The following Get-AutomaticTranscriptionSettings convenience function (source code in the bottom section) queries the registry directly in order to report the effective policy settings and therefore doesn't require the GroupPolicy module, which would require installation of the RSAT tools.

Sample call:

PS> Get-AutomaticTranscriptionSettings

Enabled                     : True
OutputPathPattern           : C:\Users\jdoe\Documents\2*\PowerShell_transcript.*.*.*.txt
IncludeInvocationHeaders    : False
PSEdition                   : Core
UsesWindowsPowerShellPolicy : True
GPOScope                    : Machine
RegistryKey                 : {HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\PowerShellCore\Transcription, HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription}

The above shows an enabled PowerShell (Core) policy at the machine level that delegates to the Windows PowerShell policy (at the same level)

Note that the OutputPathPattern property value can be passed directly to Get-ChildItem to list all existing transcripts.


Get-AutomaticTranscriptionSettings source code:

function Get-AutomaticTranscriptionSettings {
  <#
.SYNOPSIS
  Reports the current automatic transcription settings for PowerShell, if any.
.DESCRIPTION
  Targets the edition that is running the script by default, but you change
  that with -Edition, which accepts 'Core' or 'Desktop' (Windows PowerShell)

  If transcribing is enabled, the .OutputPathPattern property of the output 
  object can be passed to Get-ChildItem -Path to find all current transcripts.

  Note that transcripts are stored in calendar-day-specific subfolders using the
  "yyyDDmm" format, with the transcripts file names matching pattern
  "PowerShell_transcript.*.*.*.txt"
#>

  [CmdletBinding()]
  param(
    [ValidateSet('Core', 'Desktop')]
    [Alias('PSEdition')]
    [string] $Edition
  )

  if ($env:OS -ne 'Windows_NT') { throw 'This command runs on Window only.' }

  Set-StrictMode -Version 1

  if (-not $Edition) {
    # Default to the running edition.
    $Edition = ('Desktop', 'Core')[$PSVersionTable.PSEdition -eq 'Core']
  }
  # Normalize the casing
  $Edition = [char]::ToUpperInvariant($Edition[0]) + $Edition.Substring(1).ToLowerInvariant()

  $regKeys = [ordered] @{
    Desktop = [ordered] @{
      HKLM = Get-Item -ErrorAction Ignore -LiteralPath 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription'
      HKCU = Get-Item -ErrorAction Ignore -LiteralPath 'HKCU:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription'
    }
    Core    = [ordered] @{
      HKLM = Get-Item -ErrorAction Ignore -LiteralPath 'HKLM:\SOFTWARE\Policies\Microsoft\PowerShellCore\Transcription'
      HKCU = Get-Item -ErrorAction Ignore -LiteralPath 'HKCU:\SOFTWARE\Policies\Microsoft\PowerShellCore\Transcription'
    }    
  }

  $effectiveValue = $null # The *absence* of the value is the same as 0
  $effectiveKey = $originalKey = $null
  $usesWinPSPolicy = $false
  foreach ($hive in 'HKLM', 'HKCU') {
    if ($null -ne ($key = $regKeys.$Edition.$hive) -and $null -ne ($value = $key.GetValue('EnableTranscripting'))) {
      if ($Edition -eq 'Core' -and 1 -eq $key.GetValue('UseWindowsPowerShellPolicySetting')) {        
        $usesWinPSPolicy = $true
        $originalKey = $key
        # Use the WinPS settings in the *same hive*
        $key = $regKeys.Desktop.$hive
        $value = if ($null -ne $key) { $key.GetValue('EnableTranscripting') }
      }
      $effectiveKey = $key
      $effectiveValue = $value
      break
    }
  }

  # Construct the output object.
  # Note that the `$(...)` enclosure around the `if` statement is needed for WinPS-compatibility.
  [pscustomobject] @{      
    Enabled                     = 1 -eq $effectiveValue # only 1, specifically, is recognized as the enabling number.
    OutputPathPattern           = $(if ($effectiveKey) { 
        $dir = $effectiveKey.GetValue('OutputDirectory')
        if (-not $dir) { $dir = [System.Environment]::GetFolderPath('MyDocuments') }
        # Output dirs are calendar-day specific "yyyyMMdd" dirs., inside of which each session
        # is transcribed in a "PowerShell_transcript.*.*.*.txt" file.
        Join-Path $dir 2*\PowerShell_transcript.*.*.*.txt
      })
    IncludeInvocationHeaders    = $(if ($effectiveKey) { 1 -eq $effectiveKey.GetValue('EnableInvocationHeader') })
    PSEdition                   = $Edition
    UsesWindowsPowerShellPolicy = $(if ($null -ne $effectiveValue) { if ($Edition -eq 'Desktop') { $true } else { $usesWinPSPolicy } })
    GPOScope                    = $(if ($effectiveKey) { ('User', 'Machine')[$effectiveKey -like 'HKEY_LOCAL_MACHINE\*'] })
    RegistryKey                 = $(if ($usesWinPSPolicy -and $originalKey) { $originalKey.ToString() } if ($effectiveKey) { $effectiveKey.ToString() })
  }

}