Copy-Item from USB Does Not Work as a Job - powershell

I'm copying files to a USB utilizing the path of the USB (assuming one drive plugged in) rather than the drive letter. I need to do it this way since I'm using this to clone 52 USBs at once.
This is where the prefix for the path is found:
$usb = Get-Volume | where {$_.DriveType -eq "Removable" }
$usb.Path
Which when concatenated with the rest of the file name becomes something like:
\\?\Volume{df4d4042-9133-11e9-9a6b-48a472473e67}\RDC_2.1.1_ALL.dmg
When I call Copy-Item outside of a job everything works and copies properly. When I call Copy-Item within a PowerShell job it fails and complains about the path. This doesn't make any sense to me.
Can someone tell me why my function copyFilesSync works properly, while copyFiles doesn't work?
For simplicity, I have cut the script back to just the elements that reproduce this issue. There is no practical difference between $filestoCopyJSON1 and $filestoCopyJSON2. Swapping the actual files doesn't make a difference. I just needed two file names so I could run the two functions back-to-back and they were not copying the same file:
$filestoCopyJSON1 = '[
{
"FromFile": "C:\\dev\\VMware-player-12.1.1-3770994.exe",
"ToFile": "\\\\?\\Volume{df4d4042-9133-11e9-9a6b-48a472473e67}\\VMware-player-12.1.1-3770994.exe"
}
]'
$filestoCopyJSON2 = '[
{
"FromFile": "C:\\dev\\RDC_2.1.1_ALL.dmg",
"ToFile": "\\\\?\\Volume{df4d4042-9133-11e9-9a6b-48a472473e67}\\RDC_2.1.1_ALL.dmg"
}
]'
function copyFiles($filestoCopy){
Write-Host "Copying $($filestoCopy.Count)"
$copyblock = {
$copy = $args[0]
Write-Host "$($copy.FromFile)" "$($copy.ToFile)"
Copy-Item "$($copy.FromFile)" "$($copy.ToFile)"
$resultHash = Get-FileHash -LiteralPath "$($copy.ToFile)"
}
foreach ($filetoCopy in $filestoCopy) {
Start-Job -ScriptBlock $copyblock -ArgumentList #($filetoCopy)
}
Write-Host "Waiting on copying..."
Get-Job | Wait-Job
Write-Host "Files Copied"
}
function copyFilesSync($filestoCopy){
foreach ($filetoCopy in $filestoCopy) {
Copy-Item "$($filetoCopy.FromFile)" "$($filetoCopy.ToFile)"
$resultHash = Get-FileHash -LiteralPath "$($filetoCopy.ToFile)"
}
}
Remove-Job -Name *
Write-Host "Sync File Copy"
copyFilesSync -filestoCopy $(ConvertFrom-JSON -InputObject $filestoCopyJSON1)
Write-Host "Async File Copy"
copyFiles -filestoCopy $(ConvertFrom-JSON -InputObject $filestoCopyJSON2)
Receive-Job -Name *
This results in the following output:
PS C:\WINDOWS\system32> C:\dev\Test.ps1
Sync File Copy
Async File Copy
Copying 1
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
207 Job207 BackgroundJob Running True localhost ...
Waiting on copying...
207 Job207 BackgroundJob Completed True localhost ...
Files Copied
C:\dev\RDC_2.1.1_ALL.dmg \\?\Volume{df4d4042-9133-11e9-9a6b-48a472473e67}\RDC_2.1.1_ALL.dmg
Illegal characters in path.
+ CategoryInfo : NotSpecified: (:) [Copy-Item], ArgumentException
+ FullyQualifiedErrorId : System.ArgumentException,Microsoft.PowerShell.Commands.CopyItemCommand
+ PSComputerName : localhost
The first file from the Copy-Item that's not in a job completes successfully. The async Copy-Item run through the job fails with the errors as have been seen.

I believe this is a bug in Powershell Jobs or Copy-Item. I'm not really sure which. I was able to bypass this by utilizing Background Runspaces instead of Jobs. As a result the following async code runs just fine:
function copyFiles($filestoCopy){
Write-Host "Copying $($filestoCopy.Count)"
$copyblock = {
$filestoCopy = $args[0]
foreach($filetoCopy in $filestoCopy){
Copy-Item "$($filetoCopy.FromFile)" "$($filetoCopy.ToFile)"
$resultHash = Get-FileHash -LiteralPath "$($filetoCopy.ToFile)"
if($filetoCopy.FromFileHash -ne $resultHash.hash){
Write-Host "File $($copy.ToFile) Failed.. trying again"
Copy-Item "$($filetoCopy.FromFile)" "$($filetoCopy.ToFile)"
}
}
}
$newPowerShell = [PowerShell]::Create().AddScript($copyblock).AddArgument($filestoCopy)
$job = $newPowerShell.BeginInvoke()
Write-Host "Waiting on copying..."
While (-Not $job.IsCompleted) {}
$result = $newPowerShell.EndInvoke($job)
$newPowerShell.Dispose()
Write-Host "Files Copied"
}

Related

How can I send a node of xml to a script in Powershell?

I am making a powershell script that will be a shecdualed task on the server and that performs following things:
look in the export folder if there are export files older than 14 days and remove them ifso.
Calculated the date of yesterday and place it in a variable
Get a script, load it into a variable and adapt some arguments in it.
Extract a node out of the script into a variable and use it in the Cterm.exe application that starts afterwards.
Everything works, except the last step. The Cterm program starts, but asks a username and password. This is specified in the last variable with node of adapted script. It seems like he don't get that information and gets stuck. I want to send the content of the node to that last variable, but I get an error.
This is the error when I perform the code stepwise and another PowerShell session:
Method invocation failed because [System.String] does not contain a method named 'SelectSingleNode'.
At line:1 char:1
+ $inputscript = $tempfile.SelectSingleNode("//Text")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
This is the complete script:
#Place arguments in defined variables:
$filelocation="C:\temp" #E on the servers
$exportdestination="C:\temp"
$tempfile= ("D:\temp\temp-file.txt")
Write-Output $filelocation
$instanceName="GCS"
#Check if there are files (in the output folder) older then 14 days and remove them:
$limit = (Get-Date).AddDays(-15)
# Delete files older than the $limit.
$removedfile= Get-ChildItem -Path $exportdestination -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit }
try {
$removedfile | remove-Item -Force
}
catch {
{Write-Output "error: Not all the files could be removed!"}
}
#Export the file with the data of yesterday using the RAT script:
$yesterday=(Get-Date).AddDays(-1).ToString("yyyy-MM-dd")
write-output "hello world"
$script= [xml](Get-Content C:\CTWS_export_infinity_v1.6.04.dms)
write-output "hello world"
$script -Replace ':startDate', $yesterday -Replace ':endDate', $yesterday -Replace ':filename', ($exportdestination+"\"+$instanceName)| Out-File -FilePath $tempfile
write-output "hello world"
#| Set-Content C:\CTWS_export_infinity_v1.6.04_$yesterday.dms
#$scripttemp = C:\CTWS_export_infinity_v1.6.04_$yesterday.dms
#Start-Process -FilePath "D:\CobasInfinity\HealthShare\bin\CTerm.exe" -ArgumentList #("/console=cn_ap:$instanceName $filelocation\temp-file.txt _SYSTEM INFINITY") -wait
$inputscript = $tempfile.SelectSingleNode("//Text")
& "D:\CobasInfinity\HealthShare\bin\CTerm.exe" /console=cn_ap:$instanceName $inputscript
#Remove-Item -Path $tempfile
How can I solve that final error?
I tried to save the content as an xml in the variable,
I tried to select one node of that variable,
I used 'hello world' prints to follow the progress of the script,
I tried to check the script by doing it step by step in another PowerShell session to see where to problem is.
How can I solve that final error?

get-content : Cannot find path 'PATH_TO_FILE_' because it does not exsist

I am writing a PS Script to try and locate a folder on a remote server. Here is the code:
# Set file to be tested for, put everything after c:\
# “c:\Users\Default” is the example path
$filetofind = "EventLogs"
# Hostnames TXT Location
$hostnamestxt = "C:\Scripts\Powershell_Remote_File_Query\hosts.txt"
# Destination file for Online Machines
$onlinetxt = "C:\Scripts\Powershell_Remote_File_Query\Machines_with_file.txt"
# Destination file for Offline Machines
$offlinetxt = "C:\Scripts\Powershell_Remote_File_Query\Offline_Machines.txt"
##########################################################
# Begin Executing Script – Do Not Edit Below This Line
##########################################################
$computers = get-content “$hostnamestxt”
write-host “———————————————-”
write-host “Scanning hostnames from $hostnamestxt…”
write-host “———————————————-”
foreach($computer in $computers)
{
ping -n 1 $computer >$null
if($lastexitcode -eq 0)
{
if(test-path “\\$computer\c$\$filetofind”)
{
echo “$computer” | Out-File -Append “$onlinetxt”
write-host “File FOUND on $computer”
}
else
{write-host “File NOT found on $computer”}
}
else
{
echo “$computer” | Out-File -Append “$offlinetxt”
write-host “$computer is OFFLINE/DID NOT RESPOND TO PING”
}
}
write-host “———————————————-”
write-host “Script has completed please check output.”
write-host “Hosts with file output location – $onlinetxt”
write-host “Hosts that were unpingable output location – $offlinetxt”
write-host “———————————————-“
I get this error when running the script:
get-content : Cannot find path 'C:\Scripts\Powershell_Remote_File_Query\“€' because it does not exist.
At C:\Scripts\Powershell_Remote_File_Query\Powershell_Remote_File_Query_Script.ps1:24 char:18
+ $computers = get-content “$hostnamestxtâ€
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Scripts\Powe...ile_Query\“€:String) [Get-Content], ItemNotFoundEx
ception
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Both the script itself and the txt file are in the same directory. Please forgive me I'm new to Powershell and not a programmer by any means.
Instead of doing this...
$hostnamestxt = "C:\Scripts\Powershell_Remote_File_Query\hosts.txt"
$computers = get-content “$hostnamestxt”
Try just doing this...
$computers = get-content "C:\Scripts\Powershell_Remote_File_Query\hosts.txt"
I understand what you are trying to do should result to the same thing, yet, let's just try to eliminate that extra thing, and give it a shot to see if you get a different result.
Also, if that file you are reading is on a remote host, then you should be using the UNC file share path, not your local C: drive. Just assuming a bit here.

Wait for a script to finish

I am using a script which backups the folders and than in the next block tries to delete those folder from there original location.This is the script
if ($confirmation -eq 'y') {
# 3. BACKUP script
./bakup_mysite.ps1
# 4. DELETE CONTENTS OF my_site
get-childitem "C:\inetpub\wwwroot\my_site\" -recurse | % {
remove-item $_.FullName -recurse -force
}
}
If I put a Read-Host after step 3 it does stop and ask the user to press any key and then it deletes the next block. But I want to put a wait so the user doesn't have to press any key and everything happens automatically.
This is the backup code which gets called from my_site.ps1
$Service_folder_name = 'C:\Services\'
$Pics_folder_name = 'C:\Pics\'
$Date = Get-Date
$folder_date = $Date.ToString("yyyy-MM-dd_HHmm")
$backup_folder_name = 'c:\_Backups\my_site\' + $folder_date
if (!(Test-Path -path $backup_folder_name)) {
New-Item $backup_folder_name -type directory
}
if ((Test-Path -path $Pics_folder_name)) {
gi $pics_folder_name | .\Library\out-zip.ps1 $backup_folder_name\pics.zip $_
}
if ((Test-Path -path $Service_folder_name)) {
gi $Service_folder_name | .\Library\out-zip.ps1 $backup_folder_name\Services.zip $_
}
For Powershell cmdlets or functions, PowerShell waits before starting the next command. If it is not the case for your backup script, the trick is to pipeline to Out-Null :
./bakup_mysite.ps1 | Out-Null
PowerShell will wait until your script has exited before continuing.
Another option is to use a background job:
$BackupJob = Start-Job -FilePath "\Path\To\bakup_mysite.ps1"
Wait-Job $BackupJob
Powershell will wait until the job $BackupJob has completed before moving on to the next commands.

Automated profile update Powershell - drive not found

I am struggling with my script - for some reason, the PSDrive that my script creates is not accessible for Resolve-Path.
In general, in the script there is "Start-RDP" function which starts RDP with preloaded credentials (autologon), and then checks if the Powershell profile on the target host is up to date (by comparing the filehashes). However, in order for the script to access the remote filesystem I need to mount it as PSDrive.
Here is the script that is offending. All the variables are set properly during that time, above in the script.
New-PSDrive -name "$computername" -Root "\\$computername\c$" -Credential $CurrentCred -PSProvider FileSystem | out-null
Start-Sleep -Seconds 10
while (!(Test-Path -Path ${Computername}:\$Userpath\$Documents\)) { Write-host "UserDir not created yet!" ; start-sleep -Seconds 5 }
if (Test-Path -Path ${Computername}:\$Userpath\$Documents\WindowsPowerShell) {
$ProfileHash = Get-FileHash $Profile.CurrentUserAllHosts
if (!(Test-Path "${computername}:\$Userpath\$Documents\WindowsPowerShell\profile.ps1")) { Copy-Item -Force -Path "$env:userprofile\WindowsPowershell\profile.ps1" -Destination "${computername}:\$Userpath\$Documents\WindowsPowerShell\" }
$RemoteProfileHash = Get-FileHash "${computername}:\$Userpath\$Documents\WindowsPowerShell\profile.ps1"
if ($ProfileHash -ne $RemoteProfileHash) { Copy-Item -Force -Path "$env:userprofile\$Documents\WindowsPowershell\profile.ps1" -Destination "${computername}:\$userpath\$Documents\WindowsPowerShell\" }
}
The error I am getting is at second Test-Path (where I check if WindowsPowerShell directory exists).
Resolve-Path : Cannot find drive. A drive with the name 'server01' does not exist.
At C:\windows\system32\windowspowershell\v1.0\Modules\Microsoft.PowerShell.Utility\Microsoft.PowerShell.Utility.psm1:35 char:32
+ $pathsToProcess += Resolve-Path $Path | Foreach-Object ProviderPath
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (server01:String) [Resolve-Path], DriveNotFoundException
+ FullyQualifiedErrorId : DriveNotFound,Microsoft.PowerShell.Commands.ResolvePathCommand
I am unable to trace down the specific reason this error occurs. The drive is there (I checked using PSBreakpoint)
I'm kind of stuck at this for some time now, do you have any ideas on that one?
I see what you did there.
The problem is that you are using the variable $Profile.CurrentUserAllHosts which powershell is trying to resolve as a complete variable name. $Profile is a string, which has no property called CurrentUserAllHosts. To fix, use the following:
$ProfileHash = Get-FileHash "${Profile}.CurrentUserAllHosts"
After some more investigation, I found this snippet on a blog
commands like Resolve-Path and $PSCmdlet.GetUnresolvedProviderPathFromPSPath() don’t normalize UNC paths properly, even when the FileSystem provider handles them.
Which then links to the Get-NormalizedFileSystemPath script on technet.
Since Get-FileHash is a system provided method, you'll want to Get-NormalizedFileSystemPath before passing it to Get-FileHash
And for posterity sake, here's the script:
function Get-NormalizedFileSystemPath
{
<#
.Synopsis
Normalizes file system paths.
.DESCRIPTION
Normalizes file system paths. This is similar to what the Resolve-Path cmdlet does, except Get-NormalizedFileSystemPath also properly handles UNC paths and converts 8.3 short names to long paths.
.PARAMETER Path
The path or paths to be normalized.
.PARAMETER IncludeProviderPrefix
If this switch is passed, normalized paths will be prefixed with 'FileSystem::'. This allows them to be reliably passed to cmdlets such as Get-Content, Get-Item, etc, regardless of Powershell's current location.
.EXAMPLE
Get-NormalizedFileSystemPath -Path '\\server\share\.\SomeFolder\..\SomeOtherFolder\File.txt'
Returns '\\server\share\SomeOtherFolder\File.txt'
.EXAMPLE
'\\server\c$\.\SomeFolder\..\PROGRA~1' | Get-NormalizedFileSystemPath -IncludeProviderPrefix
Assuming you can access the c$ share on \\server, and PROGRA~1 is the short name for "Program Files" (which is common), returns:
'FileSystem::\\server\c$\Program Files'
.INPUTS
String
.OUTPUTS
String
.NOTES
Paths passed to this command cannot contain wildcards; these will be treated as invalid characters by the .NET Framework classes which do the work of validating and normalizing the path.
.LINK
Resolve-Path
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[Alias('PSPath', 'FullName')]
[string[]]
$Path,
[switch]
$IncludeProviderPrefix
)
process
{
foreach ($_path in $Path)
{
$_resolved = $_path
if ($_resolved -match '^([^:]+)::')
{
$providerName = $matches[1]
if ($providerName -ne 'FileSystem')
{
Write-Error "Only FileSystem paths may be passed to Get-NormalizedFileSystemPath. Value '$_path' is for provider '$providerName'."
continue
}
$_resolved = $_resolved.Substring($matches[0].Length)
}
if (-not [System.IO.Path]::IsPathRooted($_resolved))
{
$_resolved = Join-Path -Path $PSCmdlet.SessionState.Path.CurrentFileSystemLocation -ChildPath $_resolved
}
try
{
$dirInfo = New-Object System.IO.DirectoryInfo($_resolved)
}
catch
{
$exception = $_.Exception
while ($null -ne $exception.InnerException)
{
$exception = $exception.InnerException
}
Write-Error "Value '$_path' could not be parsed as a FileSystem path: $($exception.Message)"
continue
}
$_resolved = $dirInfo.FullName
if ($IncludeProviderPrefix)
{
$_resolved = "FileSystem::$_resolved"
}
Write-Output $_resolved
}
} # process
} # function Get-NormalizedFileSystemPath

Start-Transcript: This host does not support transcription

I want to start a transcript on a Windows Server 2008 R2
Start-Transcript -path C:\Temp\test.txt
"Hello!"
Stop-Transcript
But PowerShell returns the following error message:
Start-Transcript : This host does not support transcription.
How it is possible to activate transcript?
Windows PowerShell v4 ISE and lower do not support transcription. You must use the command line to run the commandlet.
From PowerShell v5 Start-Transcript is supported natively in ISE.
COMPLETE ANSWER (PowerShell ISE 2.0/4.0)::
Having yet another look at this today on another server, I noticed that the latest PowerShell ISE (which also does not allow Start-Transcript) does not have an Output pane, and instead uses the new ConsolePane. Thus the function now is as follows:
Function Start-iseTranscript
{
Param(
[string]$logname = (Get-logNameFromDate -path "C:\fso" -postfix " $(hostname)" -Create)
)
$transcriptHeader = #"
**************************************
Windows PowerShell ISE Transcript Start
Start Time: $((get-date).ToString('yyyyMMddhhmmss'))
UserName: $env:username
UserDomain: $env:USERDNSDOMAIN
ComputerName: $env:COMPUTERNAME
Windows version: $((Get-WmiObject win32_operatingsystem).version)
**************************************
Transcript started. Output file is $logname
"#
$transcriptHeader >> $logname
$psISE.CurrentPowerShellTab.Output.Text >> $logname
#Keep current Prompt
if ($Global:__promptDef -eq $null)
{
$Global:__promptDef = (gci Function:Prompt).Definition
$promptDef = (gci Function:Prompt).Definition
} else
{
$promptDef = $Global:__promptDef
}
$newPromptDef = #'
if ($Host.Version.Major -eq 2)
{
if ($Global:_LastText -ne $psISE.CurrentPowerShellTab.Output.Text)
{
Compare-Object -ReferenceObject ($Global:_LastText.Split("`n")) -DifferenceObject ($psISE.CurrentPowerShellTab.Output.Text.Split("`n"))|?{$_.SideIndicator -eq "=>"}|%{
$_.InputObject.TrimEnd()}|Out-File -FilePath ($Global:_DSTranscript) -Append
$Global:_LastText = $psISE.CurrentPowerShellTab.Output.Text
}
} elseif ($Host.Version.Major -eq 4)
{
if ($Global:_LastText -ne $psISE.CurrentPowerShellTab.ConsolePane.Text)
{
Compare-Object -ReferenceObject ($Global:_LastText.Split("`n")) -DifferenceObject ($psISE.CurrentPowerShellTab.ConsolePane.Text.Split("`n"))|?{$_.SideIndicator -eq "=>"}|%{
$_.InputObject.TrimEnd()}|Out-File -FilePath ($Global:_DSTranscript) -Append
$Global:_LastText = $psISE.CurrentPowerShellTab.ConsolePane.Text
}
}
'# + $promptDef
$Global:_LastText = $psISE.CurrentPowerShellTab.Output.Text
New-Item -Path Function: -Name "Global:Prompt" -Value ([ScriptBlock]::Create($newPromptDef)) -Force|Out-Null
}
Taking over the prompt is incredibly useful for this, however keeping two copies of the Output buffer is not ideal. I've also added in TrimEnd() as PSISE 2.0 likes to append spaces to fill the entire horizontal line width. Not sure if PSISE 4.0 does this too, but it's no problem now anyway.
NEW ANSWER (PowerShell ISE 2.0)::
I have just recently returned to this problem, and there is a way of forcing every update in PowerShell ISE to log out as a command is executed. This relies on the log path being saved in a Global Variable called _DSTranscript. This variable is passed to the Start-iseTranscript function. I have then Hijacked the Prompt function to execute a compare between _LastText and the hostUI Output Text, and append the differences out to the log. It now works a treat.
Function Start-iseTranscript
{
Param(
[string]$logname = (Get-logNameFromDate -path "C:\fso" -postfix " $(hostname)" -Create)
)
$transcriptHeader = #"
**************************************
Windows PowerShell ISE Transcript Start
Start Time: $(get-date)
UserName: $env:username
UserDomain: $env:USERDNSDOMAIN
ComputerName: $env:COMPUTERNAME
Windows version: $((Get-WmiObject win32_operatingsystem).version)
**************************************
Transcript started. Output file is $logname
"#
$transcriptHeader >> $logname
$psISE.CurrentPowerShellTab.Output.Text >> $logname
#Keep current Prompt
if ($__promptDef -eq $null)
{
$__promptDef = (gci Function:Prompt).Definition
$promptDef = (gci Function:Prompt).Definition
} else
{
$promptDef = $__promptDef
}
$newPromptDef = #'
if ($global:_LastText -ne $psISE.CurrentPowerShellTab.Output.Text)
{
Compare-Object -ReferenceObject $global:_LastText.Split("`n") -DifferenceObject $psISE.CurrentPowerShellTab.Output.Text.Split("`n")|?{$_.SideIndicator -eq "=>"}|%{ $_.InputObject.TrimEnd()}|Out-File -FilePath ($Global:_DSTranscript) -Append
$global:_LastText = $psISE.CurrentPowerShellTab.Output.Text
}
'# + $promptDef
New-Item -Path Function: -Name "Global:Prompt" -Value ([ScriptBlock]::Create($newPromptDef)) -Force|Out-Null
}
ORIGINAL ANSWER::
PowerShell ISE does not natively support Transcription. There is a Scripting Guy blog about how to achieve this. Unfortunately this needs to be the last thing that is run in the script. This means that you need to remember to run it before closing the window. I wish this worked better, or there was a way to force it to run on window closure.
This is the function that produces close to the same result as the Start-Transcript feature:
Function Start-iseTranscript
{
Param(
[string]$logname = (Get-logNameFromDate -path "C:\fso" -name "log" -Create)
)
$transcriptHeader = #"
**************************************
Windows PowerShell ISE Transcript Start
Start Time: $(get-date)
UserName: $env:username
UserDomain: $env:USERDNSDOMAIN
ComputerName: $env:COMPUTERNAME
Windows version: $((Get-WmiObject win32_operatingsystem).version)
**************************************
Transcript started. Output file is $logname
"#
$transcriptHeader >> $logname
$psISE.CurrentPowerShellTab.Output.Text >> $logname
} #end function start-iseTranscript
Either accept you can't, or use a host that does support transcripts (like the console host: PowerShell.exe).
The powershell.exe will also generate this error if there is a problem writing to the log file. For example, if the log file was created by an administrator and the user doesn't have permissions to overwrite the log.
Start-Transcript : The host is not currently transcribing.
At D:\Test1.ps1:9 char:1
+ Start-Transcript -Path "$Source\logs\Test.txt"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Start-Transcript], PSInvalidOperationException
+ FullyQualifiedErrorId : InvalidOperation,Microsoft.PowerShell.Commands.StartTranscriptCommand
A good solution is to try using -Append or to make the log file unique by generating a date/time stamp.
Start-Transcript -Path "$Source\logs\Test.txt" -Append
This way a proper error message is generated.
Access to the path 'D:\Test\logs\Test.txt' is denied.
-Force has the same effect as -Append and will generate a permissions error.
Following the tip from #richard here I created a snippet that allows usage of Transaction logs where I need them (scheduled tasks), so I ended having in Windows 2008R2 the following code that can be run from powershell ISE or as a standalone script.
When run in ISE, the log information will be printed on screen
When run as a script, the log information will be saved to a file
if ($Host.Name -eq "Windows PowerShell ISE Host") {
$ISE=$true
} else {
$ISE=$false
}
if (-Not $ISE) {
$Date = Get-Date -f HHmmss_ddyyyy
Start-Transcript -Path "C:\Temp\$Date.log"
}
//////////
code here ...
//////////
if (-Not $ISE) {
Stop-Transcript
}
Tagging on to the amazing answer and work by #dwarfsoft:
if ($Host.Name -match 'ISE' -and $Host.version.Major -lt 4)
{
#Start-Transcript will not work here. Use Start-iseTranscript by #dwarfsoft above
Start-iseTranscript
}
else
{
#Start Transcript Will work here
Start-Transcript
}
In addition to the ISE (which I note the original poster LaPhi doesn't even mention), the other thing that can cause this error is if you're trying to use Start-Transcript within an Invoke-Command scriptblock. For instance if you're running the script on your client machine, and then connecting to the Windows Server 2008 R2 box via Invoke-Command so Start-Transcript outputs to the server.
When run in the local session Start-Transcript works as expected, however when using Invoke-Command the script is run within a remote session on that computer, and remote sessions have certain limitations, one of which is not supporting transcription.