Why does my powershell script not wrap my path correctly? - windows

I've been tinkering with a Powershell script to automate some Streamlink stuff, and it's going okay, but I'm having an issue with the way streamlink is passing a command along, specifically with regards to wrapping the file path.
Here is the code (forgive the terrible formatting, I am not a pro, I'm only a beginner.)
Set-Location (Split-Path $MyInvocation.MyCommand.Path)
$scriptdir = Split-Path $MyInvocation.MyCommand.Path
$streamurl = Read-Host -Prompt 'Stream URL'
$channel = youtube-dl --cookies $scriptdir\Resources\cookies.txt --write-description --skip-download -o "%(uploader)s" --get-filename $streamurl
if (-not (Test-Path -LiteralPath $scriptdir\Recordings\$channel\)) {
try {
New-Item -Path $scriptdir\Recordings\$channel\ -ItemType Directory -ErrorAction Stop | Out-Null #-Force
}
catch {
Write-Error -Message "Unable to create directory '$channel'. Error was: $_" -ErrorAction Stop
}
"Successfully created directory '$channel'."
}
streamlink-auth $scriptdir\Resources\cookies.txt --retry-streams 10 -o "$scriptdir\Recordings\$channel\stream.mp4" $streamurl best
$scriptdir = Where the script is running
$streamurl = Self explanatory
$channel = the channel name of the stream
The issue I'm having is that when this command is passed along to streamlink, it seems to be wrapping wrong, as when streamlink tries to run the command, I get the following error:
streamlink args: --retry-streams 10 -o W:\recordings\Recordings\【LIVE】新宿 大ガード交差点 Tokyo Shinjuku Live Ch\stream.mp4 https://www.youtube.com/watch?v=RQA5RcIZlAM best
usage: streamlink [OPTIONS] <URL> [STREAM]
streamlink: error: unrecognized arguments: Shinjuku Live Ch\stream.mp4 https://www.youtube.com/watch?v=RQA5RcIZlAM best
Source:【LIVE】新宿 大ガード交差点 Tokyo Shinjuku Live Ch
I also tested this with a fully english channel (with the same script) and got the following:
streamlink args: --retry-streams 10 -o W:\recordings\Recordings\Watson Amelia Ch. hololive-EN\stream.mp4 https://www.youtube.com/watch?v=EeC6OHBRFTc best
usage: streamlink [OPTIONS] [STREAM]
streamlink: error: unrecognized arguments: hololive-EN\stream.mp4 https://www.youtube.com/watch?v=EeC6OHBRFTc best
Source: Watson Amelia Ch. hololive-EN
It seems that while the directory can be created fine, passing the variables into the streamlink command fails somewhere along the line. This can be worked around by simply moving into the channel directory with:
cd $scriptdir\Recordings\$channel\
and running the command without the extra variables in the path, but that's not the way I'd like to do it. I also want to add that youtube-dl has no issues with writing files to the directories created with this script as my postprocessing script writes files there without trouble, and powershell can still remove files there too.
I have asked the streamlink folks, and it's definitely not on their end, so obviously it's a powershell thing.
How do I solve this problem, because I am genuinely stumped now.

I'm dumb.
Turns out when you pass something into a command, you have to pass it with Double quote, then single quote.
So instead of:
streamlink-auth $scriptdir\Resources\cookies.txt --retry-streams 10 -o "$scriptdir\Recordings\$channel\stream.mp4" $streamurl best
It needs to be:
streamlink-auth $scriptdir\Resources\cookies.txt --retry-streams 10 -o "'$scriptdir\Recordings\$channel\stream.mp4'" $streamurl best
Sorry for being the dum.

Related

Is there a way to make a link/symlink/shortcut to the latest file in Windows? Keep tailing the latest log file

I searched high and low, found how to do it in *nix, but nothing about Windows.
First place I've seen this was Tomcat's catalina.out, and now I was wondering how to do a similar thing on Windows: considering a folder where log files are created, how to make a file that reads the/points to latest log created?
I'm thinking a Powershell solution might be possible, but I honestly can't think or find any way to do it.
(edit) You guys downvoting could at least leave a comment to tell me what did I do wrong or how can I improve this question?
(edit) The idea here is to have some way to create a symlink that points to the latest log file in a folder, so a program can monitor always the same file, no matter if the latest file changes its name - like tail -f catalina.out always reads the latest catalina log file.
The only way out I can see, and that I wanted to avoid, would be to write a powershell script that would monitor a folder (https://superuser.com/questions/226828/how-to-monitor-a-folder-and-trigger-a-command-line-action-when-a-file-is-created) and would dynamically create a symlink to the latest file found (https://stackoverflow.com/a/11211005/1985023), then set it as a service, so it would be always running on the background.
Instead of looking for a dynamically self-updating symlink (which would be quite cumbersome to implement - see the helpful hints from BACON in the comments in the question), you can make this work as a self-contained function/script with the help of PowerShell background jobs:
Run in a loop that periodically gets the latest log-file lines from a background job that does the equivalent of Unix tail -f via Get-Content -Wait -Tail 10.
If a new log file is found, terminate the previous background job and start one for the new log file.
Note that this relies on periodic polling of the background job that tails the log. The code below allows you to adjust the polling interval.
Note that Get-Content -Wait itself polls the target file for changes every second.
Here's the code; run $VerbosePreference = 'Continue' to see what's going on inside the loop:
$dir = 'C:\path\to\logs' # the log-file directory
$logFilePattern = '*.log' # wildcard pattern matching log files
$sleepIntervalMs = 1000 # how many msec. to sleep between getting new lines from the background job
Write-Host -ForegroundColor Green "Tailing the latest log(s) in $dir...`nPress any key to quit."
$currJob = $currLog = $null
while ($true) {
# If the user pressed a key, clean up and exit.
if ([console]::KeyAvailable) {
$null = [console]::ReadKey($True) # consume the key - it will still have printed, though
if ($currJob) { Remove-Job -Job $currJob -Force }
break
}
# Get the latest lines from the current log from the background job.
if ($currJob) {
Write-Verbose "Checking for new lines in $newLog..."
Receive-Job -Job $currJob
Start-Sleep -Milliseconds $sleepIntervalMs # sleep a little
}
# Determine the first / newest log.
$newLog = Get-ChildItem -LiteralPath $dir -Filter $logFilePattern | Sort-Object CreationTimeUtc -Descending | Select-Object -First 1
if ($newLog.FullName -ne $currLog.FullName) { # new log file found.
Write-Verbose "(New) log file found: $newLog"
if ($currJob) {
Write-Verbose "Terminating background job for previous log ($currLog)."
Remove-Job -Job $currJob -Force
# When a *new* log was just started, we show *all* lines (and keep listening for more).
$tailArg = #{}
} else {
# When we first start monitoring, we start with the *last 10* lines
# of the current log (and keep listening for more).
$tailArg = #{ Tail = 10 } # On first
}
$currLog = $newLog
Write-Verbose "Starting background job for $currLog..."
# Start the background job for the new log.
$currJob = Start-Job { Get-Content -Wait #using:tailArg -LiteralPath $using:newLog.FullName }
}
}
Write-Host -ForegroundColor Green "Terminated."

PowerShell: Invoke-Parallel throwing error with "start-process"

I'm using the Invoke-Parallel CmdLet that can be found at the link here. When I have some dummy code in my script block, it works correctly, but when I add a start-process... command it fails with an error saying
Get-RunspaceData : This command cannot be run due to the error: The system
cannot find the file specified.
At c:\crm\Interfaces\Batches\Invoke-Parallel.ps1:592 char:13
My script block looks like so. Long story short, I'm feeding file names into a block, and I am telling a 3rd party application to use that file as an input for a process. When the start-process line is removed, it works, with it, it fails.
$childFiles| Invoke-Parallel -ImportVariables {
$importformat = '-f ' + '"CU Imp P19 FI"'
$importfile = '-d ' + $_ + " " + $user + " " + $pass
Write-Host $importformat + " " + $_
start-process .\mmim.exe -ArgumentList $user, $pass, '-l:1',
$importformat, $importfile -Wait -NoNewWindow
return "blah"
}
Does anyone have any idea of what might be going on? My PowerShell version is the following
Major:5
Minor:0
Build: 10586
Revision: 117
Any help is greatly appreciated.
Each PowerShell runspace have its own current location. If Invoke-Parallel does not change current location of spawned runspaces to match current location of main runspace, then relative path .\mmim.exe may be resolved to entire different executable or not resolved at all and produce given error.

I want to fetch the name of the latest updated folder at particular path of FTP server

Using this command I am able to get the latest updated folder in Unix
ls -t1 | head -1
But how can I get the same in FTP server from Windows?
I want to get the name of the latest updated folder at particular path of FTP server. Could any one please help?
There's no easy way to do this with Windows shell commands.
You can:
Use ftp.exe to execute ls /path c:\local\path\listing.txt to save a directory listing to a text file.
Exit ftp.exe.
Parse the listing and find the latest files. Not an easy task for Windows shell commands.
It would be a way easier with a PowerShell script.
You can use FtpWebRequest class. Though it does not have an easy way to retrieve structured directory listing either. It offers only ListDirectoryDetails and GetDateTimestamp methods.
See Retrieving creation date of file (FTP).
Or use a 3rd-party library for the task.
For example with WinSCP .NET assembly you can do:
param (
$sessionUrl = "ftp://user:mypassword#example.com/",
$remotePath = "/path"
)
# Load WinSCP .NET assembly
Add-Type -Path "WinSCPnet.dll"
# Setup session options
$sessionOptions = New-Object WinSCP.SessionOptions
$sessionOptions.ParseUrl($sessionUrl)
# Connect
$session = New-Object WinSCP.Session
$session.Open($sessionOptions)
# Get list of files in the directory
$directoryInfo = $session.ListDirectory($remotePath)
# Select the most recent file
$latest =
$directoryInfo.Files |
Where-Object { -Not $_.IsDirectory } |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
# Any file at all?
if ($latest -eq $Null)
{
Write-Host "No file found"
}
else
{
Write-Host "The latest file is $latest"
}
See full example Downloading the most recent file.
(I'm the author of WinSCP)

Teamcity 8- treat a build parameter as long or integer

Using teamCity 8.0.5.
As part of my builds cleanup I would like to automatically remove any build older than the current 5 builds. For eg if my build.number = 12 , try and remove build 6.
Unfortunately it seems that you can't cast %build.number%-6.
Using Teamcity how can you perform math on a build parameter?
I have tried
%build.number% -1 along with wrapping it in ()
%system.PreviousBuild% = \\server\path\Build%build.number%-6
my actual usage of this a command line post build event:
echo trying to remove directory %system.PreviousBuild%
IF EXIST %system.PreviousBuild% RD %system.PreviousBuild% /Q /S
result:
[09:19:09][Step 6/6] trying to remove director \server\path\Build11-6
-------------------------UPDATE---------------------------
the answer below led me to the following solution:
NOTE: The important part here is to cast the build.number as an integer.
using powershell source code build step
Using powershell for the entire command worked for me:
Set-Variable -Name previousBuild -Value (([int]%build.number%)-6).ToString()
Write-Output $previousBuild
Write-Output $env:PreviousBuildPath
Set-Variable -Name path -Value $env:PreviousBuildPath$previousBuild
Write-Output $path
if((Test-Path -path $path))
{
Remove-Item -Recurse -Force $path
}
set /a newBuildNumber=%build.number%-6
Then use %%newBuildNumber%% in your TC build script after the path.
\\server\path\Build%%newBuildNumber%%

Unix tail equivalent command in Windows Powershell

I have to look at the last few lines of a large file (typical size is 500MB-2GB). I am looking for a equivalent of Unix command tail for Windows Powershell. A few alternatives available on are,
http://tailforwin32.sourceforge.net/
and
Get-Content [filename] | Select-Object -Last 10
For me, it is not allowed to use the first alternative, and the second alternative is slow. Does anyone know of an efficient implementation of tail for PowerShell.
Use the -wait parameter with Get-Content, which displays lines as they are added to the file. This feature was present in PowerShell v1, but for some reason not documented well in v2.
Here is an example
Get-Content -Path "C:\scripts\test.txt" -Wait
Once you run this, update and save the file and you will see the changes on the console.
For completeness I'll mention that Powershell 3.0 now has a -Tail flag on Get-Content
Get-Content ./log.log -Tail 10
gets the last 10 lines of the file
Get-Content ./log.log -Wait -Tail 10
gets the last 10 lines of the file and waits for more
Also, for those *nix users, note that most systems alias cat to Get-Content, so this usually works
cat ./log.log -Tail 10
As of PowerShell version 3.0, the Get-Content cmdlet has a -Tail parameter that should help. See the technet library online help for Get-Content.
I used some of the answers given here but just a heads up that
Get-Content -Path Yourfile.log -Tail 30 -Wait
will chew up memory after awhile. A colleague left such a "tail" up over the last day and it went up to 800 MB. I don't know if Unix tail behaves the same way (but I doubt it). So it's fine to use for short term applications, but be careful with it.
PowerShell Community Extensions (PSCX) provides the Get-FileTail cmdlet. It looks like a suitable solution for the task. Note: I did not try it with extremely large files but the description says it efficiently tails the contents and it is designed for large log files.
NAME
Get-FileTail
SYNOPSIS
PSCX Cmdlet: Tails the contents of a file - optionally waiting on new content.
SYNTAX
Get-FileTail [-Path] <String[]> [-Count <Int32>] [-Encoding <EncodingParameter>] [-LineTerminator <String>] [-Wait] [<CommonParameters>]
Get-FileTail [-LiteralPath] <String[]> [-Count <Int32>] [-Encoding <EncodingParameter>] [-LineTerminator <String>] [-Wait] [<CommonParameters>]
DESCRIPTION
This implentation efficiently tails the cotents of a file by reading lines from the end rather then processing the entire file. This behavior is crucial for ef
ficiently tailing large log files and large log files over a network. You can also specify the Wait parameter to have the cmdlet wait and display new content
as it is written to the file. Use Ctrl+C to break out of the wait loop. Note that if an encoding is not specified, the cmdlet will attempt to auto-detect the
encoding by reading the first character from the file. If no character haven't been written to the file yet, the cmdlet will default to using Unicode encoding
. You can override this behavior by explicitly specifying the encoding via the Encoding parameter.
Probably too late for an answere but, try this one
Get-Content <filename> -tail <number of items wanted> -wait
Just some additions to previous answers. There are aliases defined for Get-Content, for example if you are used to UNIX you might like cat, and there are also type and gc. So instead of
Get-Content -Path <Path> -Wait -Tail 10
you can write
# Print whole file and wait for appended lines and print them
cat <Path> -Wait
# Print last 10 lines and wait for appended lines and print them
cat <Path> -Tail 10 -Wait
I have a useful tip on this subject concerning multiple files.
Following a single log file (like 'tail -f' in Linux) with PowerShell 5.2 (Win7 and Win10) is easy (just use "Get-Content MyFile -Tail 1 -Wait"). However, watching MULTIPLE log files at once seems complicated. With PowerShell 7.x+ however, I've found an easy way by using "Foreach-Object -Parrallel". This performs multiple 'Get-Content' commands concurrently. For example:
Get-ChildItem C:\logs\*.log | Foreach-Object -Parallel { Get-Content $_ -Tail 1 -Wait }
Using Powershell V2 and below, get-content reads the entire file, so it was of no use to me. The following code works for what I needed, though there are likely some issues with character encodings. This is effectively tail -f, but it could be easily modified to get the last x bytes, or last x lines if you want to search backwards for line breaks.
$filename = "\wherever\your\file\is.txt"
$reader = new-object System.IO.StreamReader(New-Object IO.FileStream($filename, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [IO.FileShare]::ReadWrite))
#start at the end of the file
$lastMaxOffset = $reader.BaseStream.Length
while ($true)
{
Start-Sleep -m 100
#if the file size has not changed, idle
if ($reader.BaseStream.Length -eq $lastMaxOffset) {
continue;
}
#seek to the last max offset
$reader.BaseStream.Seek($lastMaxOffset, [System.IO.SeekOrigin]::Begin) | out-null
#read out of the file until the EOF
$line = ""
while (($line = $reader.ReadLine()) -ne $null) {
write-output $line
}
#update the last max offset
$lastMaxOffset = $reader.BaseStream.Position
}
I found most of the code to do this here.
I took #hajamie's solution and wrapped it up into a slightly more convenient script wrapper.
I added an option to start from an offset before the end of the file, so you can use the tail-like functionality of reading a certain amount from the end of the file. Note the offset is in bytes, not lines.
There's also an option to continue waiting for more content.
Examples (assuming you save this as TailFile.ps1):
.\TailFile.ps1 -File .\path\to\myfile.log -InitialOffset 1000000
.\TailFile.ps1 -File .\path\to\myfile.log -InitialOffset 1000000 -Follow:$true
.\TailFile.ps1 -File .\path\to\myfile.log -Follow:$true
And here is the script itself...
param (
[Parameter(Mandatory=$true,HelpMessage="Enter the path to a file to tail")][string]$File = "",
[Parameter(Mandatory=$true,HelpMessage="Enter the number of bytes from the end of the file")][int]$InitialOffset = 10248,
[Parameter(Mandatory=$false,HelpMessage="Continuing monitoring the file for new additions?")][boolean]$Follow = $false
)
$ci = get-childitem $File
$fullName = $ci.FullName
$reader = new-object System.IO.StreamReader(New-Object IO.FileStream($fullName, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [IO.FileShare]::ReadWrite))
#start at the end of the file
$lastMaxOffset = $reader.BaseStream.Length - $InitialOffset
while ($true)
{
#if the file size has not changed, idle
if ($reader.BaseStream.Length -ge $lastMaxOffset) {
#seek to the last max offset
$reader.BaseStream.Seek($lastMaxOffset, [System.IO.SeekOrigin]::Begin) | out-null
#read out of the file until the EOF
$line = ""
while (($line = $reader.ReadLine()) -ne $null) {
write-output $line
}
#update the last max offset
$lastMaxOffset = $reader.BaseStream.Position
}
if($Follow){
Start-Sleep -m 100
} else {
break;
}
}
try Windows Server 2003 Resource Kit Tools
it contains a tail.exe which can be run on Windows system.
https://www.microsoft.com/en-us/download/details.aspx?id=17657
There have been many valid answers, however, none of them has the same syntax as tail in linux. The following function can be stored in your $Home\Documents\PowerShell\Microsoft.PowerShell_profile.ps1 for persistency (see powershell profiles documentation for more details).
This allows you to call...
tail server.log
tail -n 5 server.log
tail -f server.log
tail -Follow -Lines 5 -Path server.log
which comes quite close to the linux syntax.
function tail {
<#
.SYNOPSIS
Get the last n lines of a text file.
.PARAMETER Follow
output appended data as the file grows
.PARAMETER Lines
output the last N lines (default: 10)
.PARAMETER Path
path to the text file
.INPUTS
System.Int
IO.FileInfo
.OUTPUTS
System.String
.EXAMPLE
PS> tail c:\server.log
.EXAMPLE
PS> tail -f -n 20 c:\server.log
#>
[CmdletBinding()]
[OutputType('System.String')]
Param(
[Alias("f")]
[parameter(Mandatory=$false)]
[switch]$Follow,
[Alias("n")]
[parameter(Mandatory=$false)]
[Int]$Lines = 10,
[parameter(Mandatory=$true, Position=5)]
[ValidateNotNullOrEmpty()]
[IO.FileInfo]$Path
)
if ($Follow)
{
Get-Content -Path $Path -Tail $Lines -Wait
}
else
{
Get-Content -Path $Path -Tail $Lines
}
}
Very basic, but does what you need without any addon modules or PS version requirements:
while ($true) {Clear-Host; gc E:\test.txt | select -last 3; sleep 2 }
It is possible to download all of the UNIX commands compiled for Windows from this GitHub repository: https://github.com/George-Ogden/UNIX
For those admins who live by the axiom that less typing is best, here is the shortest version I can find:
gc filename -wai -ta 10

Resources