The goal is to reset the current cmd.exe shell to have the original set of environment variables. This should include deleting current environment variables that were created after the cmd.exe shell started.
The System and User environment variables can be read from the registry. But, the dynamic variables such as ALLUSERSPROFILE, APPDATA, LOGONSERVER, etc. are not in those locations. Where can those be found?
Because of this, the code cannot delete a variable created after the cmd.exe shell was started. This is because it may be one of the dynamic variables.
Put both of these files in the same directory.
=== Do-Environment.bat
#ECHO OFF
SET "TEMPFILE=%TEMP%\do-environment-script.bat"
powershell -NoLogo -NoProfile -File "%~dp0Do-Environment.ps1" >"%TEMPFILE%"
type "%TEMPFILE%"
CALL "%TEMPFILE%"
EXIT /B %ERRORLEVEL%
=== Do-Environment.ps1
$Vars = #{}
$UserVars = 'HKCU:\Environment'
$SystemVars = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment'
(Get-Item -Path $SystemVars).Property |
ForEach-Object {
$KeyName = $_
$KeyValue = (Get-Item -Path $SystemVars).GetValue($KeyName, $null)
$Vars[$KeyName] = $KeyValue
}
(Get-Item -Path $UserVars).Property |
ForEach-Object {
$KeyName = $_
$KeyValue = (Get-Item -Path $UserVars).GetValue($KeyName, $null)
if (($null -eq $Vars[$KeyName]) -or ($KeyName -ne 'Path')) {
$Vars[$KeyName] = $KeyValue
} else {
$Vars[$KeyName] = $Vars[$KeyName] + ';' + $KeyValue
}
#'SET "{0}={1}"' -f #($KeyName, $KeyValue)
}
$Vars.Keys | Sort-Object | ForEach-Object { 'SET "{0}={1}"' -f #($_, $Vars[$_]) }
Related
I am attempting to extract the date last modified from the files in a Windows directory. Here is my basic script:
Function Get-FolderItem {
[cmdletbinding(DefaultParameterSetName='Filter')]
Param (
[parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[Alias('FullName')]
[string[]]$Path = $PWD,
[parameter(ParameterSetName='Filter')]
[string[]]$Filter = '*.*',
[parameter(ParameterSetName='Exclude')]
[string[]]$ExcludeFile,
[parameter()]
[int]$MaxAge,
[parameter()]
[int]$MinAge
)
Begin {
$params = New-Object System.Collections.Arraylist
$params.AddRange(#("/L","/E","/NJH","/NDL","/BYTES","/FP","/NC","/XJ","/R:0","/W:0","T:W","/TS","/UNILOG:c:\temp\test.txt"))
#params.AddRange(#("/L","/S","/NJH","/BYTES","/FP","/NC","/NDL","/TS","/XJ","/R:0","/W:0"))
If ($PSBoundParameters['MaxAge']) {
$params.Add("/MaxAge:$MaxAge") | Out-Null
}
If ($PSBoundParameters['MinAge']) {
$params.Add("/MinAge:$MinAge") | Out-Null
}
}
Process {
ForEach ($item in $Path) {
Try {
$item = (Resolve-Path -LiteralPath $item -ErrorAction Stop).ProviderPath
If (-Not (Test-Path -LiteralPath $item -Type Container -ErrorAction Stop)) {
Write-Warning ("{0} is not a directory and will be skipped" -f $item)
Return
}
If ($PSBoundParameters['ExcludeFile']) {
$Script = "robocopy `"$item`" NULL $Filter $params /XF $($ExcludeFile -join ',')"
} Else {
$Script = "robocopy `"$item`" NULL $Filter $params"
}
Write-Verbose ("Scanning {0}" -f $item)
Invoke-Expression $Script | Out-Null
get-content "c:\temp\test.txt" | ForEach {
Try {
If ($_.Trim() -match "^(?<Children>\d+)\s(?<FullName>.*)") {
$object = New-Object PSObject -Property #{
FullName = $matches.FullName
Extension = $matches.fullname -replace '.*\.(.*)','$1'
FullPathLength = [int] $matches.FullName.Length
FileHash = Get-FileHash -LiteralPath "\\?\$($matches.FullName)" |Select -Expand Hash
Created = ([System.IO.FileInfo] $matches.FullName).creationtime
LastWriteTime = ([System.IO.FileInfo] $matches.FullName).LastWriteTime
Characters = (Get-Content -LiteralPath "\\?\$($matches.FullName)" | Measure-Object -ignorewhitespace -Character).Characters
Owner = (Get-ACL $matches.Fullname).Owner
}
$object.pstypenames.insert(0,'System.IO.RobocopyDirectoryInfo')
Write-Output $object
} Else {
Write-Verbose ("Not matched: {0}" -f $_)
}
} Catch {
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
} Catch {
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
}
}
$a = Get-FolderItem "C:\TargetDirectory\Folder" | Export-Csv -Path C:\Temp\output.csv -Encoding Unicode
The script extracts the date last modified of filepaths less than 260 characters. It returns a nonsense date of 1600-12-31 4:00:00 PM for files longer than 260 characters. Here is the line that is not working:
LastWriteTime = ([System.IO.FileInfo] $matches.FullName).LastWriteTime
My first attempt to solve this problem was to find a command that began with Get- because such commands were useful in extracting filehashes, filepaths, character counts and owner names of files longer than 260 characters. For example:
Owner = (Get-ACL $matches.Fullname).Owner
Characters = (Get-Content -LiteralPath "\\?\$($matches.FullName)" | Measure-Object-ignorewhitespace -Character).Characters
FileHash = Get-FileHash -LiteralPath "\\?\$($matches.FullName)" |Select -Expand Hash
Get-Date however seemed to be about getting the current date.
In my second attempt, I went back to Boe Prox's original blogpost on this script and noticed that his script had two components that were missing from mine:
a robocopy switch /TS
Date = [datetime]$matches.Date
I added to my script however doing so return an error: WARNING: Cannot convert null to type "System.DateTime". I rechecked the file in the directory, and it clearly has a date.
I reexamined the documentation on Get-Date and tried
Date = Get-Date -Format o | ForEach-Object { $matches -replace ":", "." }
However, this returned WARNING: Cannot convert value "2018/03/05 18:06:54 C:TargetDirectory\Folder\Temp.csv to type "System.IO.FileInfo". Error: " Illegal characters in path."
(N.B. In other posts, people have suggested changing the server settings to permit the existence of files longer than 260 characters. This is not an option for me because I do not have access to the servers.)
Once you hit 260 characters in the path, you hit the old Windows MAX_PATH limitation. In order to get around that, you have to prepend your path with \\?\.
In your code above, you do that for Characters and FileHash but you don't do that when retrieving LastWriteTime. e.g. Changing the path to this will work:
Created = ([System.IO.FileInfo] "\\?\$($matches.FullName)").creationtime
LastWriteTime = ([System.IO.FileInfo] "\\?\$($matches.FullName)").LastWriteTime
The alternative way is to use the Get-ChildItem cmdlet along with \\?\ prepended to the path to retrieve most of the fields you want without having to query it multiple times:
get-content "c:\temp\test.txt" | ForEach {
Try {
If ($_.Trim() -match "^(?<Children>\d+)\s(?<FullName>.*)") {
$file = Get-ChildItem "\\?\$($matches.FullName)"
$object = New-Object PSObject -Property #{
FullName = $file.FullName
Extension = $file.Extension
FullPathLength = $file.FullName.Length
FileHash = Get-FileHash -LiteralPath "\\?\$($matches.FullName)" |Select -Expand Hash
Created = $file.CreationTime
LastWriteTime = $file.LastWriteTime
Characters = (Get-Content -LiteralPath "\\?\$($matches.FullName)" | Measure-Object -ignorewhitespace -Character).Characters
Owner = (Get-ACL $matches.Fullname).Owner
}
$object.pstypenames.insert(0,'System.IO.RobocopyDirectoryInfo')
Write-Output $object
} Else {
Write-Verbose ("Not matched: {0}" -f $_)
}
} Catch {
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
This process starts on my computer every time I log in:
C:\WINDOWS\system32\cmd.exe /d /s /c "powershell -Command "& { [Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8; $ErrorActionPreference = 'SilentlyContinue'; $applications = New-Object Collections.Generic.List[String]; 'C:\ProgramData\Microsoft\Windows\Start Menu\Programs','C:\Users\Z\AppData\Roaming\Microsoft\Windows\Start Menu','C:\Users\Z\Desktop' | %{ Get-ChildItem -Path $_ -include *.lnk, *.appref-ms, *.url, *.exe -Recurse -File | % { $applications.Add($_.FullName) } }; $result = (#{ errors = #($error | ForEach-Object { $_.Exception.Message }); applications = $applications } | ConvertTo-Json); Write-Host $result; }""
I don't know enough Powershell to view exactly what it does.
Thanks
I'm currently writing a PowerShell script for the first time. I just want to find out which Java versions I have running on my Windows machines. It is searching for all java.exe instants, writes it in a file and then it should execute each line and write the output in a file. But I can't use my variable $command to execute anything. So the for loop is just running one time, because of an error and then quits.
#declaration
$file = ".\text.txt"
$i = 3
$out = ".\output.txt"
Get-ChildItem -Path C:\ -Filter java.exe -Recurse -ErrorAction SilentlyContinue -Force |
Select-Object Directory >> $file
$count = Get-Content $file | Measure-Object -Line
#remove spaces and append "java.exe"
(Get-Content $file | Foreach {$_.TrimEnd()}) | Set-Content $file
(Get-Content $file | foreach {$_ + "\java.exe' -version"}) | Set-Content $file
(Get-Content $file).Replace("C:", "'C:") | Set-Content $file
#remove last 2 lines of the file
$count = $count.Lines - 2
#execute the stored paths
$i = 3
for ($i=3; $i -le $count; $i++) {
$command = Get-Content $file | Select -Index $i;
$command >> $out;
& $command 2> $out; # <----------------This line wont work
echo "_____________________" >> $out;
}
Although I'm not sure what your desired output should look like, but below creates an array of PSObjects that you can output on screen or write to CSV file.
$result = Get-ChildItem -Path C:\ -Filter java.exe -File -Recurse -ErrorAction SilentlyContinue -Force | ForEach-Object {
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = $_.FullName
$pinfo.Arguments = "-version"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $pinfo
$process.Start() | Out-Null
$process.WaitForExit()
$output = $process.StandardOutput.ReadToEnd()
if (!$output) { $output = $process.StandardError.ReadToEnd() }
[PsCustomObject]#{
'FileName' = $_.FullName
'VersionInfo' = $output
}
}
# output on screen
$result | Format-List
# output to CSV file (VersionInfo field uses multiple lines)
$result | Export-Csv -Path 'D:\javaversions.csv' -UseCulture -NoTypeInformation
Result on screen wil look something like:
FileName : C:\Program Files\Java\jdk1.8.0_31\bin\java.exe
VersionInfo : java version "1.8.0_31"
Java(TM) SE Runtime Environment (build 1.8.0_31-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)
FileName : C:\Program Files\Java\jdk1.8.0_31\jre\bin\java.exe
VersionInfo : java version "1.8.0_31"
Java(TM) SE Runtime Environment (build 1.8.0_31-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)
FileName : C:\Program Files\Java\jre1.8.0_221\bin\java.exe
VersionInfo : java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)
Your code doesn't work because you're writing entire commandlines to the intermediate file and then try to invoke the command strings from this file via the call operator. That doesn't work because a command "'C:\path\to\java.exe' -version" doesn't exist. You should be getting an error like this:
PS C:\> $command = "'C:\path\to\java.exe' -version"
PS C:\> & $command
& : The term ''C:\path\to\java.exe' -version' is not recognized as the name of
a cmdlet, function, script file, or operable program. Check the spelling of the
name, or if a path was included, verify that the path is correct and try again.
At line:1 char:3
+ & $command
+ ~~~~~~~~
+ CategoryInfo : ObjectNotFound: ('C:\path\to\java.exe' -version:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
The simplest way to make your code work is to just invoke the executables directly:
Get-ChildItem -Path 'C:\' -Filter 'java.exe' -Recurse -EA SilentlyContinue -Force |
ForEach-Object {
$_.FullName + ' -version'
& $_.FullName -version 2>&1
'__________________'
} |
Set-Content $out
If you want to stick with the approach using an intermediate file you need to prepend the commandline with the call operator, convert it to a scriptblock, and then invoke that.
Get-Content $file | ForEach-Object {
$command
& ([Scriptblock]::Create("& $command")) 2>&1
'__________________'
} | Out-File $out
An alternative solution to #Theo is using a temporary file like so:
$GetParams = #{
Path = 'C:\'
Filter = 'java.exe'
Recurse = $true
Force = $true
File = $true
ErrorAction = 'SilentlyContinue'
}
$JavaExeFiles = Get-ChildItem #GetParams | select -First 1
$StartParams = #{
ArgumentList = '-version'
NoNewWindow = $true
RedirectStandardError = '.\javaver.txt'
}
$JaveExeDetails = foreach ($JaveExe in $JavaExeFiles) {
Remove-Item -Path $StartParams.RedirectStandardError -EA Ignore
Start-Process #StartParams -FilePath $JaveExe.FullName -Wait
[PSCustomObject]#{
File = $JaveExe.FullName
Version = Get-Content -Path $StartParams.RedirectStandardError
}
}
# For viewing in the console
$JaveExeDetails | fl *
# For export to a file
$JaveExeDetails | fl * | Out-File -FilePath ./JavaVersions.txt
#echo off
setlocal
set _RunOnceValue=%~d0%\Windows10Upgrade\Windows10UpgraderApp.exe /SkipSelfUpdate
set _RunOnceKey=Windows10UpgraderApp.exe
REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce" /V "%_RunOnceKey%" /t REG_SZ /F /D "%_RunOnceValue%"
PowerShell -Command "&{ Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Used -gt 0 } | ForEach-Object { $esdOriginalFilePath = 'C:\Windows10Upgrade\\*.esd'; $driveName = $_.Name; $esdFilePath = $esdOriginalFilePath -replace '^\w',$driveName; if (Test-Path $esdFilePath) { Remove-Item $esdFilePath } } }"
Found this batch script hidden somewhere
in my C: Drive and want to know what does this script do.
This is the Powershell portion of the script:
Get-PSDrive -PSProvider FileSystem |
Where-Object { $_.Used -gt 0 } |
ForEach-Object {
$esdOriginalFilePath = 'C:\Windows10Upgrade\\*.esd';
$driveName = $_.Name;
$esdFilePath = $esdOriginalFilePath -replace '^\w',$driveName;
if (Test-Path $esdFilePath)
{ Remove-Item $esdFilePath }
}
The Powershell part:
Gets your drive mappings
Checks which have used up space in them (probably all of them)
Loops through these and removes any files that are in Windows10Upgrade folders that have the extension .esd
e.g.
M:\Windows10Upgrade\*.esd
S:\Windows10Upgrade\*.esd
T:\Windows10Upgrade\*.esd
See below for a commented version of the code, explaining each line:
# Get shared drives
Get-PSDrive -PSProvider FileSystem |
# Filter just those with used space
Where-Object { $_.Used -gt 0 } |
# Go through each of those drives
ForEach-Object {
# Set a variable with the Windows10Upgrade folder and *.esd wildcard
$esdOriginalFilePath = 'C:\Windows10Upgrade\\*.esd';
# Get the drive name for the one currently in the loop ($_)
$driveName = $_.Name;
# Use a regex replace of the first 'word' character with drivename (e.g. C -> E, C -> W)
$esdFilePath = $esdOriginalFilePath -replace '^\w',$driveName;
# Check if the path exists
if (Test-Path $esdFilePath)
# If so, remove the file
{ Remove-Item $esdFilePath }
}
I would suggest running this in Powershell:
Get-PSDrive -PSProvider FileSystem |
Where-Object { $_.Used -gt 0 }
To get an idea of how this works.
I'm working on a script that checks folders in specific directory. For example, I run the script for first time, it generates me a txt file containing folders in the directory.
I need the script to add any new directories that are found to the previously created txt file when the script is run again.
Does anyone have any suggestions how to make that happen?
Here is my code so far:
$LogFolders = Get-ChildItem -Directory mydirectory ;
If (-Not (Test-Path -path "txtfilelocated"))
{
Add-Content txtfilelocated -Value $LogFolders
break;
}else{
$File = Get-Content "txtfilelocatedt"
$File | ForEach-Object {
$_ -match $LogFolders
}
}
$File
something like this?
You can specify what directory to check adding path to get-childitem cmdlet in first line
$a = get-childitem | ? { $_.psiscontainer } | select -expand fullname #for V2.0 and above
$a = get-childitem -Directory | select -expand fullname #for V3.0 and above
if ( test-path .\list.txt )
{
compare-object (gc list.txt) ($a) -PassThru | Add-Content .\list.txt
}
else
{
$a | set-content .\list.txt
}