diff --git a/storagebatchoperations/api/README.md b/storagebatchoperations/api/README.md
new file mode 100644
index 00000000000..fb019dfbcb2
--- /dev/null
+++ b/storagebatchoperations/api/README.md
@@ -0,0 +1,31 @@
+# Cloud Storage Batch Operations Sample
+
+These samples demonstrate how to interact with the [Google Cloud Storage Batch Operations API][Storage Batch Operations] from C# and
+the .NET client libraries to call the Storage Batch Operations API.
+
+The samples require [.NET 8][.NET].That means using
+[Visual Studio 2022](https://www.visualstudio.com/), or the command line.
+
+## Setup
+
+1. Set up a [.NET development environment](https://cloud.google.com/dotnet/docs/setup).
+
+2. Enable APIs for your project.
+ [Click here][enable-api]
+ to visit Cloud Platform Console and enable the Google Cloud Storage Batch Operations API.
+
+## Contributing changes
+
+* See [CONTRIBUTING.md](../../CONTRIBUTING.md)
+
+## Licensing
+
+* See [LICENSE](../../LICENSE)
+
+## Testing
+
+* See [TESTING.md](../../TESTING.md)
+
+[Storage Batch Operations]: https://cloud.google.com/storage/docs/batch-operations/overview
+[enable-api]: https://console.cloud.google.com/flows/enableapi?apiid=storagebatchoperations_api&showconfirmation=true
+[.NET]: https://dotnet.microsoft.com/en-us/download
diff --git a/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/CancelBatchJobTest.cs b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/CancelBatchJobTest.cs
new file mode 100644
index 00000000000..b781eeee53b
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/CancelBatchJobTest.cs
@@ -0,0 +1,141 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License").
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Google.Api.Gax.ResourceNames;
+using Google.Cloud.StorageBatchOperations.V1;
+using Google.LongRunning;
+using System;
+using System.IO;
+using System.Text;
+using System.Threading;
+using Xunit;
+
+[Collection(nameof(StorageFixture))]
+public class CancelBatchJobTest
+{
+ private readonly StorageFixture _fixture;
+ private readonly BucketList.Types.Bucket _bucket;
+ private readonly BucketList _bucketList = new();
+ private readonly PrefixList _prefixListObject = new();
+
+ public CancelBatchJobTest(StorageFixture fixture)
+ {
+ int i = 20;
+ _fixture = fixture;
+ var bucketName = _fixture.GenerateBucketName();
+ _fixture.CreateBucket(bucketName, multiVersion: false, softDelete: false, registerForDeletion: true);
+ // Uploading objects to the bucket.
+ while (i >= 0)
+ {
+ var objectName = _fixture.GenerateGuid();
+ var objectContent = _fixture.GenerateGuid();
+ byte[] byteObjectContent = Encoding.UTF8.GetBytes(objectContent);
+ MemoryStream streamObjectContent = new MemoryStream(byteObjectContent);
+ _fixture.Client.UploadObject(bucketName, objectName, "application/text", streamObjectContent);
+ i--;
+ }
+
+ _bucket = new BucketList.Types.Bucket
+ {
+ Bucket_ = bucketName,
+ // The prefix list is used to specify the objects to be deleted. To match all objects, use an empty list.
+ PrefixList = _prefixListObject
+ };
+ // Adding the bucket to the bucket list.
+ _bucketList.Buckets.Insert(0, _bucket);
+ }
+
+ [Fact]
+ public void TestCancelBatchJob()
+ {
+ CancelBatchJobSample cancelBatchJob = new CancelBatchJobSample();
+ ListBatchJobsSample listBatchJobs = new ListBatchJobsSample();
+ GetBatchJobSample getBatchJob = new GetBatchJobSample();
+
+ string filter = "state:canceled";
+ int pageSize = 10;
+ string orderBy = "create_time";
+
+ var jobId = _fixture.GenerateGuid();
+ var createdJobName = CreateBatchJob(_fixture.LocationName, _bucketList, jobId);
+ Assert.NotNull(createdJobName);
+
+ // Poll until the job is in a state that can be cancelled (Running)
+ // This prevents attempting to cancel a job that hasn't started or has already finished.
+ bool isCancellable = false;
+ for (int attempt = 0; attempt < 10; attempt++)
+ {
+ Job currentJob = getBatchJob.GetBatchJob(createdJobName);
+ if (currentJob.State == Job.Types.State.Running)
+ {
+ isCancellable = true;
+ break;
+ }
+ if (currentJob.State == Job.Types.State.Succeeded) break;
+ }
+
+ Assert.True(isCancellable, "Job did not reach a Running state in time to be cancelled.");
+ cancelBatchJob.CancelBatchJob(createdJobName);
+
+ Job finalJob = null;
+ for (int attempt = 0; attempt < 1000; attempt++)
+ {
+ finalJob = getBatchJob.GetBatchJob(createdJobName);
+ if (finalJob.State == Job.Types.State.Canceled) break;
+ }
+ Assert.Equal(createdJobName, finalJob.Name.ToString());
+ Assert.Equal(Job.Types.State.Canceled, finalJob.State);
+
+ var batchJobs = listBatchJobs.ListBatchJobs(_fixture.LocationName, filter, pageSize, orderBy);
+ Assert.Contains(batchJobs, j => j.Name == createdJobName);
+ _fixture.DeleteBatchJob(createdJobName);
+ }
+
+ ///
+ /// Create a batch job with the specified transformation case and bucket list.
+ ///
+ public static string CreateBatchJob(LocationName locationName,
+ BucketList bucketList,
+ string jobId = "12345678910")
+ {
+ StorageBatchOperationsClient storageBatchClient = StorageBatchOperationsClient.Create();
+
+ // Creates a batch job with the specified bucket list and delete object settings.
+ CreateJobRequest request = new CreateJobRequest
+ {
+ ParentAsLocationName = locationName,
+ JobId = jobId,
+ Job = new Job
+ {
+ BucketList = bucketList,
+ DeleteObject = new DeleteObject { PermanentObjectDeletionEnabled = true }
+ },
+ RequestId = jobId,
+ };
+
+ Operation response = storageBatchClient.CreateJob(request);
+ string jobName = String.Empty;
+ for (int attempt = 0; attempt < 10; attempt++)
+ {
+ Operation retrievedResponse = storageBatchClient.PollOnceCreateJob(response.Name);
+ jobName = retrievedResponse.Metadata?.Job?.Name;
+
+ if (!string.IsNullOrEmpty(jobName))
+ {
+ break;
+ }
+ }
+ return jobName;
+ }
+}
diff --git a/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/CreateBatchJobWithDeleteObjectTest.cs b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/CreateBatchJobWithDeleteObjectTest.cs
new file mode 100644
index 00000000000..8f4eb162473
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/CreateBatchJobWithDeleteObjectTest.cs
@@ -0,0 +1,80 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License").
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Google.Cloud.StorageBatchOperations.V1;
+using System;
+using System.IO;
+using System.Text;
+using Xunit;
+
+[Collection(nameof(StorageFixture))]
+public class CreateBatchJobWithDeleteObjectTest
+{
+ private readonly StorageFixture _fixture;
+ private readonly BucketList.Types.Bucket _bucket = new();
+ private readonly BucketList _bucketList = new();
+ private readonly PrefixList _prefixListObject = new();
+
+ public CreateBatchJobWithDeleteObjectTest(StorageFixture fixture)
+ {
+ _fixture = fixture;
+ var bucketName = _fixture.GenerateBucketName();
+ _fixture.CreateBucket(bucketName, multiVersion: false, softDelete: false, registerForDeletion: true);
+
+ var manifestBucketName = _fixture.GenerateBucketName();
+ _fixture.CreateBucket(manifestBucketName, multiVersion: false, softDelete: false, registerForDeletion: true);
+
+ var objectName = _fixture.GenerateGuid();
+ var manifestObjectName = _fixture.GenerateGuid();
+ var objectContent = _fixture.GenerateGuid();
+ var manifestObjectContent = $"bucket,name,generation{Environment.NewLine}{bucketName},{objectName}";
+
+ byte[] byteObjectContent = Encoding.UTF8.GetBytes(objectContent);
+ using MemoryStream streamObjectContent = new MemoryStream(byteObjectContent);
+ // Uploading an object to the bucket
+ _fixture.Client.UploadObject(bucketName, objectName, "application/text", streamObjectContent);
+
+ byte[] byteManifestObjectContent = Encoding.UTF8.GetBytes(manifestObjectContent);
+ // Uploading a manifest object to the manifest bucket
+ using MemoryStream streamManifestObjectContent = new MemoryStream(byteManifestObjectContent);
+ _fixture.Client.UploadObject(manifestBucketName, $"{manifestObjectName}.csv", "text/csv", streamManifestObjectContent);
+ _bucket = new BucketList.Types.Bucket
+ {
+ Bucket_ = bucketName,
+ // The prefix list is used to specify the objects to be transformed. To match all objects, use an empty list.
+ PrefixList = _prefixListObject,
+ // Manifest location contains csv file having list of objects to be transformed"
+ Manifest = new Manifest { ManifestLocation = $"gs://{manifestBucketName}/{manifestObjectName}.csv" }
+ };
+ // Adding the bucket to the bucket list.
+ _bucketList.Buckets.Insert(0, _bucket);
+ }
+
+ [Fact]
+ public void TestCreateBatchJobWithDeleteObject()
+ {
+ CreateBatchJobWithDeleteObjectSample createJob = new CreateBatchJobWithDeleteObjectSample();
+ var jobId = _fixture.GenerateGuid();
+
+ var createdBatchJob = createJob.CreateBatchJobWithDeleteObject(_fixture.LocationName, _bucketList, jobId);
+ Assert.Equal(createdBatchJob.BucketList, _bucketList);
+ Assert.Equal("DeleteObject", createdBatchJob.TransformationCase.ToString());
+ Assert.Equal(createdBatchJob.SourceCase.ToString(), _bucketList.GetType().Name);
+ Assert.NotNull(createdBatchJob.Name);
+ Assert.NotNull(createdBatchJob.JobName);
+ Assert.NotNull(createdBatchJob.CreateTime);
+ Assert.NotNull(createdBatchJob.CompleteTime);
+ _fixture.DeleteBatchJob(createdBatchJob.Name);
+ }
+}
diff --git a/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/CreateBatchJobWithPutMetaDataTest.cs b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/CreateBatchJobWithPutMetaDataTest.cs
new file mode 100644
index 00000000000..cd2a5edb21b
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/CreateBatchJobWithPutMetaDataTest.cs
@@ -0,0 +1,80 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License").
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Google.Cloud.StorageBatchOperations.V1;
+using System;
+using System.IO;
+using System.Text;
+using Xunit;
+
+[Collection(nameof(StorageFixture))]
+public class CreateBatchJobWithPutMetaDataTest
+{
+ private readonly StorageFixture _fixture;
+ private readonly BucketList.Types.Bucket _bucket = new();
+ private readonly BucketList _bucketList = new();
+ private readonly PrefixList _prefixListObject = new();
+
+ public CreateBatchJobWithPutMetaDataTest(StorageFixture fixture)
+ {
+ _fixture = fixture;
+ var bucketName = _fixture.GenerateBucketName();
+ _fixture.CreateBucket(bucketName, multiVersion: false, softDelete: false, registerForDeletion: true);
+
+ var manifestBucketName = _fixture.GenerateBucketName();
+ _fixture.CreateBucket(manifestBucketName, multiVersion: false, softDelete: false, registerForDeletion: true);
+
+ var objectName = _fixture.GenerateGuid();
+ var manifestObjectName = _fixture.GenerateGuid();
+ var objectContent = _fixture.GenerateGuid();
+ var manifestObjectContent = $"bucket,name,generation{Environment.NewLine}{bucketName},{objectName}";
+
+ byte[] byteObjectContent = Encoding.UTF8.GetBytes(objectContent);
+ using MemoryStream streamObjectContent = new MemoryStream(byteObjectContent);
+ // Uploading an object to the bucket
+ _fixture.Client.UploadObject(bucketName, objectName, "application/text", streamObjectContent);
+
+ byte[] byteManifestObjectContent = Encoding.UTF8.GetBytes(manifestObjectContent);
+ // Uploading a manifest object to the manifest bucket
+ using MemoryStream streamManifestObjectContent = new MemoryStream(byteManifestObjectContent);
+ _fixture.Client.UploadObject(manifestBucketName, $"{manifestObjectName}.csv", "text/csv", streamManifestObjectContent);
+ _bucket = new BucketList.Types.Bucket
+ {
+ Bucket_ = bucketName,
+ // The prefix list is used to specify the objects to be transformed. To match all objects, use an empty list.
+ PrefixList = _prefixListObject,
+ // Manifest location contains csv file having list of objects to be transformed"
+ Manifest = new Manifest { ManifestLocation = $"gs://{manifestBucketName}/{manifestObjectName}.csv" }
+ };
+ // Adding the bucket to the bucket list.
+ _bucketList.Buckets.Insert(0, _bucket);
+ }
+
+ [Fact]
+ public void TestCreateBatchJobWithPutMetaData()
+ {
+ CreateBatchJobWithPutMetadataSample createJob = new CreateBatchJobWithPutMetadataSample();
+ var jobId = _fixture.GenerateGuid();
+
+ var createdBatchJob = createJob.CreateBatchJobWithPutMetadata(_fixture.LocationName, _bucketList, jobId);
+ Assert.Equal(createdBatchJob.BucketList, _bucketList);
+ Assert.Equal("PutMetadata", createdBatchJob.TransformationCase.ToString());
+ Assert.Equal(createdBatchJob.SourceCase.ToString(), _bucketList.GetType().Name);
+ Assert.NotNull(createdBatchJob.Name);
+ Assert.NotNull(createdBatchJob.JobName);
+ Assert.NotNull(createdBatchJob.CreateTime);
+ Assert.NotNull(createdBatchJob.CompleteTime);
+ _fixture.DeleteBatchJob(createdBatchJob.Name);
+ }
+}
diff --git a/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/CreateBatchJobWithPutObjectHoldTest.cs b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/CreateBatchJobWithPutObjectHoldTest.cs
new file mode 100644
index 00000000000..01925673c2e
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/CreateBatchJobWithPutObjectHoldTest.cs
@@ -0,0 +1,80 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License").
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Google.Cloud.StorageBatchOperations.V1;
+using System;
+using System.IO;
+using System.Text;
+using Xunit;
+
+[Collection(nameof(StorageFixture))]
+public class CreateBatchJobWithPutObjectHoldTest
+{
+ private readonly StorageFixture _fixture;
+ private readonly BucketList.Types.Bucket _bucket = new();
+ private readonly BucketList _bucketList = new();
+ private readonly PrefixList _prefixListObject = new();
+
+ public CreateBatchJobWithPutObjectHoldTest(StorageFixture fixture)
+ {
+ _fixture = fixture;
+ var bucketName = _fixture.GenerateBucketName();
+ _fixture.CreateBucket(bucketName, multiVersion: false, softDelete: false, registerForDeletion: true);
+
+ var manifestBucketName = _fixture.GenerateBucketName();
+ _fixture.CreateBucket(manifestBucketName, multiVersion: false, softDelete: false, registerForDeletion: true);
+
+ var objectName = _fixture.GenerateGuid();
+ var manifestObjectName = _fixture.GenerateGuid();
+ var objectContent = _fixture.GenerateGuid();
+ var manifestObjectContent = $"bucket,name,generation{Environment.NewLine}{bucketName},{objectName}";
+
+ byte[] byteObjectContent = Encoding.UTF8.GetBytes(objectContent);
+ using MemoryStream streamObjectContent = new MemoryStream(byteObjectContent);
+ // Uploading an object to the bucket
+ _fixture.Client.UploadObject(bucketName, objectName, "application/text", streamObjectContent);
+
+ byte[] byteManifestObjectContent = Encoding.UTF8.GetBytes(manifestObjectContent);
+ // Uploading a manifest object to the manifest bucket
+ using MemoryStream streamManifestObjectContent = new MemoryStream(byteManifestObjectContent);
+ _fixture.Client.UploadObject(manifestBucketName, $"{manifestObjectName}.csv", "text/csv", streamManifestObjectContent);
+ _bucket = new BucketList.Types.Bucket
+ {
+ Bucket_ = bucketName,
+ // The prefix list is used to specify the objects to be transformed. To match all objects, use an empty list.
+ PrefixList = _prefixListObject,
+ // Manifest location contains csv file having list of objects to be transformed"
+ Manifest = new Manifest { ManifestLocation = $"gs://{manifestBucketName}/{manifestObjectName}.csv" }
+ };
+ // Adding the bucket to the bucket list.
+ _bucketList.Buckets.Insert(0, _bucket);
+ }
+
+ [Fact]
+ public void TestCreateBatchJobWithPutObjectHold()
+ {
+ CreateBatchJobWithPutObjectHoldSample createJob = new CreateBatchJobWithPutObjectHoldSample();
+ var jobId = _fixture.GenerateGuid();
+
+ var createdBatchJob = createJob.CreateBatchJobWithPutObjectHold(_fixture.LocationName, _bucketList, jobId);
+ Assert.Equal(createdBatchJob.BucketList, _bucketList);
+ Assert.Equal("PutObjectHold", createdBatchJob.TransformationCase.ToString());
+ Assert.Equal(createdBatchJob.SourceCase.ToString(), _bucketList.GetType().Name);
+ Assert.NotNull(createdBatchJob.Name);
+ Assert.NotNull(createdBatchJob.JobName);
+ Assert.NotNull(createdBatchJob.CreateTime);
+ Assert.NotNull(createdBatchJob.CompleteTime);
+ _fixture.DeleteBatchJob(createdBatchJob.Name);
+ }
+}
diff --git a/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/CreateBatchJobWithRewriteObjectTest.cs b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/CreateBatchJobWithRewriteObjectTest.cs
new file mode 100644
index 00000000000..304dac88d8b
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/CreateBatchJobWithRewriteObjectTest.cs
@@ -0,0 +1,83 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License").
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Google.Cloud.StorageBatchOperations.V1;
+using System;
+using System.IO;
+using System.Text;
+using Xunit;
+
+[Collection(nameof(StorageFixture))]
+public class CreateBatchJobWithRewriteObjectTest
+{
+ private readonly StorageFixture _fixture;
+ private readonly BucketList.Types.Bucket _bucket = new();
+ private readonly BucketList _bucketList = new();
+ private readonly PrefixList _prefixListObject = new();
+
+ public CreateBatchJobWithRewriteObjectTest(StorageFixture fixture)
+ {
+ _fixture = fixture;
+ var bucketName = _fixture.GenerateBucketName();
+ _fixture.CreateBucket(bucketName, multiVersion: false, softDelete: false, registerForDeletion: true);
+
+ var manifestBucketName = _fixture.GenerateBucketName();
+ _fixture.CreateBucket(manifestBucketName, multiVersion: false, softDelete: false, registerForDeletion: true);
+
+ var objectName = _fixture.GenerateGuid();
+ var manifestObjectName = _fixture.GenerateGuid();
+ var objectContent = _fixture.GenerateGuid();
+ var manifestObjectContent = $"bucket,name,generation{Environment.NewLine}{bucketName},{objectName}";
+
+ byte[] byteObjectContent = Encoding.UTF8.GetBytes(objectContent);
+ using MemoryStream streamObjectContent = new MemoryStream(byteObjectContent);
+ // Uploading an object to the bucket
+ _fixture.Client.UploadObject(bucketName, objectName, "application/text", streamObjectContent);
+
+ byte[] byteManifestObjectContent = Encoding.UTF8.GetBytes(manifestObjectContent);
+ // Uploading a manifest object to the manifest bucket
+ using MemoryStream streamManifestObjectContent = new MemoryStream(byteManifestObjectContent);
+ _fixture.Client.UploadObject(manifestBucketName, $"{manifestObjectName}.csv", "text/csv", streamManifestObjectContent);
+ _bucket = new BucketList.Types.Bucket
+ {
+ Bucket_ = bucketName,
+ // The prefix list is used to specify the objects to be transformed. To match all objects, use an empty list.
+ PrefixList = _prefixListObject,
+ // Manifest location contains csv file having list of objects to be transformed"
+ Manifest = new Manifest { ManifestLocation = $"gs://{manifestBucketName}/{manifestObjectName}.csv" }
+ };
+ // Adding the bucket to the bucket list.
+ _bucketList.Buckets.Insert(0, _bucket);
+ }
+
+ [Fact]
+ public void TestCreateBatchJobWithRewriteObject()
+ {
+ CreateBatchJobWithRewriteObjectSample createJob = new CreateBatchJobWithRewriteObjectSample();
+ var jobId = _fixture.GenerateGuid();
+
+ string kmsKeyName = $"projects/{_fixture.ProjectId}/locations/{_fixture.LocationId}/keyRings/{_fixture.KmsKeyRing}/cryptoKeys/{_fixture.KmsKeyName}";
+ var cryptoKeyName = CryptoKeyName.FromProjectLocationKeyRingCryptoKey(_fixture.ProjectId, _fixture.LocationId, _fixture.KmsKeyRing, _fixture.KmsKeyName);
+
+ var createdBatchJob = createJob.CreateBatchJobWithRewriteObject(_fixture.LocationName, _bucketList, jobId, kmsKeyName, cryptoKeyName);
+ Assert.Equal(createdBatchJob.BucketList, _bucketList);
+ Assert.Equal("RewriteObject", createdBatchJob.TransformationCase.ToString());
+ Assert.Equal(createdBatchJob.SourceCase.ToString(), _bucketList.GetType().Name);
+ Assert.NotNull(createdBatchJob.Name);
+ Assert.NotNull(createdBatchJob.JobName);
+ Assert.NotNull(createdBatchJob.CreateTime);
+ Assert.NotNull(createdBatchJob.CompleteTime);
+ _fixture.DeleteBatchJob(createdBatchJob.Name);
+ }
+}
diff --git a/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/DeleteBatchJobTest.cs b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/DeleteBatchJobTest.cs
new file mode 100644
index 00000000000..fbeb3da4d92
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/DeleteBatchJobTest.cs
@@ -0,0 +1,64 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License").
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Google.Cloud.StorageBatchOperations.V1;
+using Xunit;
+
+[Collection(nameof(StorageFixture))]
+public class DeleteBatchJobTest
+{
+ private readonly StorageFixture _fixture;
+ private readonly BucketList.Types.Bucket _bucket;
+ private readonly BucketList _bucketList = new();
+ private readonly PrefixList _prefixListObject = new();
+
+ public DeleteBatchJobTest(StorageFixture fixture)
+ {
+ _fixture = fixture;
+ var bucketName = _fixture.GenerateBucketName();
+ _fixture.CreateBucket(bucketName, multiVersion: false, softDelete: false, registerForDeletion: true);
+ _bucket = new BucketList.Types.Bucket
+ {
+ Bucket_ = bucketName,
+ // The prefix list is used to specify the objects to be deleted. To match all objects, use an empty list.
+ PrefixList = _prefixListObject
+ };
+ // Adding the bucket to the bucket list.
+ _bucketList.Buckets.Insert(0, _bucket);
+ }
+
+ [Fact]
+ public void TestDeleteBatchJob()
+ {
+ DeleteBatchJobSample deleteBatchJob = new DeleteBatchJobSample();
+ ListBatchJobsSample listBatchJobs = new ListBatchJobsSample();
+ GetBatchJobSample getBatchJob = new GetBatchJobSample();
+ CreateBatchJobWithDeleteObjectSample createBatchJob = new CreateBatchJobWithDeleteObjectSample();
+
+ string filter = "";
+ int pageSize = 10;
+ string orderBy = "create_time";
+
+ var jobId = _fixture.GenerateGuid();
+ var createdJob = createBatchJob.CreateBatchJobWithDeleteObject(_fixture.LocationName, _bucketList, jobId);
+ // Delete the created job.
+ deleteBatchJob.DeleteBatchJob(createdJob.Name);
+ var batchJobs = listBatchJobs.ListBatchJobs(_fixture.LocationName, filter, pageSize, orderBy);
+ // Verify that the job is deleted.
+ Assert.DoesNotContain(batchJobs, job => job.JobName == createdJob.JobName);
+ // Attempt to get the deleted job, which should throw an exception.
+ var exception = Assert.Throws(() => getBatchJob.GetBatchJob(createdJob.Name));
+ Assert.Equal(Grpc.Core.StatusCode.NotFound, exception.StatusCode);
+ }
+}
diff --git a/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/GetBatchJobTest.cs b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/GetBatchJobTest.cs
new file mode 100644
index 00000000000..fb1d9837e88
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/GetBatchJobTest.cs
@@ -0,0 +1,67 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License").
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Google.Cloud.StorageBatchOperations.V1;
+using Xunit;
+
+[Collection(nameof(StorageFixture))]
+public class GetBatchJobTest
+{
+ private readonly StorageFixture _fixture;
+ private readonly BucketList.Types.Bucket _bucket;
+ private readonly BucketList _bucketList = new();
+ private readonly PrefixList _prefixListObject = new();
+
+ public GetBatchJobTest(StorageFixture fixture)
+ {
+ _fixture = fixture;
+ var bucketName = _fixture.GenerateBucketName();
+ _fixture.CreateBucket(bucketName, multiVersion: false, softDelete: false, registerForDeletion: true);
+ _bucket = new BucketList.Types.Bucket
+ {
+ Bucket_ = bucketName,
+ // The prefix list is used to specify the objects to be deleted. To match all objects, use an empty list.
+ PrefixList = _prefixListObject
+ };
+ // Adding the bucket to the bucket list.
+ _bucketList.Buckets.Insert(0, _bucket);
+ }
+
+ [Fact]
+ public void TestGetBatchJob()
+ {
+ GetBatchJobSample getJob = new GetBatchJobSample();
+ CreateBatchJobWithDeleteObjectSample createJob = new CreateBatchJobWithDeleteObjectSample();
+
+ var jobId = _fixture.GenerateGuid();
+ // Create a batch job with the specified bucket list and job ID.
+ var createdJob = createJob.CreateBatchJobWithDeleteObject(_fixture.LocationName, _bucketList, jobId);
+ // Get the created job using its name.
+ var retrievedJob = getJob.GetBatchJob(createdJob.Name);
+ // Assert that the retrieved job is not null.
+ Assert.NotNull(retrievedJob);
+ // Assert that the retrieved job's metadata matches with the created job's metadata.
+ Assert.Equal(createdJob.Name, retrievedJob.Name);
+ Assert.Equal(createdJob.BucketList, retrievedJob.BucketList);
+ Assert.Equal(createdJob.TransformationCase.ToString(), retrievedJob.TransformationCase.ToString());
+ Assert.Equal(createdJob.SourceCase.ToString(), retrievedJob.SourceCase.ToString());
+ Assert.Equal(createdJob.State, retrievedJob.State);
+ Assert.Equal(createdJob.Description, retrievedJob.Description);
+ Assert.Equal(createdJob.ScheduleTime, retrievedJob.ScheduleTime);
+ Assert.Equal(createdJob.CompleteTime, retrievedJob.CompleteTime);
+ Assert.Equal(createdJob.CreateTime, retrievedJob.CreateTime);
+ Assert.Equal(createdJob.Counters, retrievedJob.Counters);
+ _fixture.DeleteBatchJob(createdJob.Name);
+ }
+}
diff --git a/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/ListBatchJobsTest.cs b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/ListBatchJobsTest.cs
new file mode 100644
index 00000000000..ae7a7961c06
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/ListBatchJobsTest.cs
@@ -0,0 +1,73 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License").
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Google.Cloud.StorageBatchOperations.V1;
+using Xunit;
+
+[Collection(nameof(StorageFixture))]
+public class ListBatchJobsTest
+{
+ private readonly StorageFixture _fixture;
+ private readonly BucketList.Types.Bucket _bucket;
+ private readonly BucketList _bucketList = new();
+ private readonly PrefixList _prefixListObject = new();
+
+ public ListBatchJobsTest(StorageFixture fixture)
+ {
+ _fixture = fixture;
+ var bucketName = _fixture.GenerateBucketName();
+ _fixture.CreateBucket(bucketName, multiVersion: false, softDelete: false, registerForDeletion: true);
+ _bucket = new BucketList.Types.Bucket
+ {
+ Bucket_ = bucketName,
+ // The prefix list is used to specify the objects to be processed. To match all objects, use an empty list.
+ PrefixList = _prefixListObject
+ };
+ // Adding the bucket to the bucket list.
+ _bucketList.Buckets.Insert(0, _bucket);
+ }
+
+ [Fact]
+ public void TestListBatchJobs()
+ {
+ ListBatchJobsSample listBatchJobs = new ListBatchJobsSample();
+ CreateBatchJobWithDeleteObjectSample createBatchJob = new CreateBatchJobWithDeleteObjectSample();
+
+ // Filter to list only succeeded jobs
+ string filter = "state:succeeded";
+ int pageSize = 10;
+ string orderBy = "create_time";
+
+ var jobId = _fixture.GenerateGuid();
+ var createdJob = createBatchJob.CreateBatchJobWithDeleteObject(_fixture.LocationName, _bucketList, jobId);
+ // List batch jobs with the specified filter, page size, and order by criteria
+ var batchJobs = listBatchJobs.ListBatchJobs(_fixture.LocationName, filter, pageSize, orderBy);
+ // Assert that the created job is in the list of batch jobs.
+ Assert.Contains(batchJobs, job => job.JobName == createdJob.JobName && job.State == createdJob.State && job.SourceCase == createdJob.SourceCase && job.TransformationCase == createdJob.TransformationCase);
+ // Assert that all batch jobs have the required metadata.
+ Assert.All(batchJobs, AssertBatchJob);
+ _fixture.DeleteBatchJob(createdJob.Name);
+ }
+
+ // Verify that a batch job has all required metadata.
+ private void AssertBatchJob(Job b)
+ {
+ Assert.NotNull(b.Name);
+ Assert.NotNull(b.JobName);
+ Assert.NotNull(b.CreateTime);
+ Assert.NotNull(b.CompleteTime);
+ Assert.NotNull(b.SourceCase.ToString());
+ Assert.NotNull(b.TransformationCase.ToString());
+ }
+}
diff --git a/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/StorageBatchOperations.Samples.Tests.csproj b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/StorageBatchOperations.Samples.Tests.csproj
new file mode 100644
index 00000000000..5ec9770c75b
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/StorageBatchOperations.Samples.Tests.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net8.0
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/StorageFixture.cs b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/StorageFixture.cs
new file mode 100644
index 00000000000..47ed3e0d60c
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.Samples.Tests/StorageFixture.cs
@@ -0,0 +1,107 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Google.Api.Gax.ResourceNames;
+using Google.Apis.Storage.v1.Data;
+using Google.Cloud.Storage.V1;
+using Google.Cloud.StorageBatchOperations.V1;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using Xunit;
+
+[CollectionDefinition(nameof(StorageFixture))]
+public class StorageFixture : IDisposable, ICollectionFixture
+{
+ public string ProjectId { get; }
+ public string LocationId { get; } = "global";
+ public IList TempBucketNames { get; } = [];
+ public StorageClient Client { get; }
+ public StorageBatchOperationsClient OperationsClient { get; }
+ public LocationName LocationName { get; }
+ public string KmsKeyRing { get; } = Environment.GetEnvironmentVariable("STORAGE_KMS_KEYRING");
+ public string KmsKeyName { get; } = Environment.GetEnvironmentVariable("STORAGE_KMS_KEYNAME");
+
+ public StorageFixture()
+ {
+ ProjectId = Environment.GetEnvironmentVariable("GOOGLE_PROJECT_ID");
+ if (string.IsNullOrWhiteSpace(ProjectId))
+ {
+ throw new InvalidOperationException("You need to set the Environment variable 'GOOGLE_PROJECT_ID' with your Google Cloud Project's project id.");
+ }
+ LocationName = LocationName.FromProjectLocation(ProjectId, LocationId);
+ Client = StorageClient.Create();
+ OperationsClient = StorageBatchOperationsClient.Create();
+ }
+
+ ///
+ /// Creates a bucket.
+ ///
+ /// A bucket.
+ internal Bucket CreateBucket(string name, bool multiVersion, bool softDelete = false, bool registerForDeletion = true)
+ {
+ var bucket = Client.CreateBucket(ProjectId,
+ new Bucket
+ {
+ Name = name,
+ Versioning = new Bucket.VersioningData { Enabled = multiVersion },
+ // The minimum allowed for soft delete is 7 days.
+ SoftDeletePolicy = softDelete ? new Bucket.SoftDeletePolicyData { RetentionDurationSeconds = (int) TimeSpan.FromDays(7).TotalSeconds } : null,
+ });
+ SleepAfterBucketCreateUpdateDelete();
+ if (registerForDeletion)
+ {
+ TempBucketNames.Add(name);
+ }
+ return bucket;
+ }
+
+ ///
+ /// Generate the name of the bucket.
+ ///
+ /// The bucketName.
+ internal string GenerateBucketName() => Guid.NewGuid().ToString();
+
+ ///
+ /// Generates a new globally unique identifier (GUID).
+ ///
+ /// A new randomly generated GUID as string.
+ internal string GenerateGuid() => Guid.NewGuid().ToString();
+
+ ///
+ /// Bucket creation/update/deletion is rate-limited. To avoid making the tests flaky, we sleep after each operation.
+ ///
+ internal void SleepAfterBucketCreateUpdateDelete() => Thread.Sleep(2000);
+
+ ///
+ /// Deletes the batch job at the end of the test.
+ ///
+ internal void DeleteBatchJob(string jobName) => OperationsClient.DeleteJob(jobName);
+
+ public void Dispose()
+ {
+ foreach (var bucketName in TempBucketNames)
+ {
+ try
+ {
+ Client.DeleteBucket(bucketName, new DeleteBucketOptions { DeleteObjects = true });
+ SleepAfterBucketCreateUpdateDelete();
+ }
+ catch (Exception)
+ {
+ // Do nothing, we delete on a best effort basis.
+ }
+ }
+ }
+}
diff --git a/storagebatchoperations/api/StorageBatchOperations.Samples/CancelBatchJob.cs b/storagebatchoperations/api/StorageBatchOperations.Samples/CancelBatchJob.cs
new file mode 100644
index 00000000000..66df35bd534
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.Samples/CancelBatchJob.cs
@@ -0,0 +1,41 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License").
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// [START storage_batch_cancel_job]
+
+using Google.Cloud.StorageBatchOperations.V1;
+using System;
+
+public class CancelBatchJobSample
+{
+ ///
+ /// Cancels a storage batch operation job.
+ ///
+ /// The name of the job to cancel. Format: projects/{project_id}/locations/{location_id}/jobs/{job_id}.
+ public CancelJobResponse CancelBatchJob(string jobName = "projects/{project_id}/locations/{location_id}/jobs/{job_id}")
+ {
+ StorageBatchOperationsClient operationsClient = StorageBatchOperationsClient.Create();
+ // Create a request to cancel the job.
+ CancelJobRequest request = new CancelJobRequest
+ {
+ Name = jobName,
+ RequestId = Guid.NewGuid().ToString()
+ };
+ // Cancel the job.
+ var response = operationsClient.CancelJob(request);
+ Console.WriteLine($"The Storage Batch Operation Job (Name: {jobName}) is cancelled");
+ return response;
+ }
+}
+// [END storage_batch_cancel_job]
diff --git a/storagebatchoperations/api/StorageBatchOperations.Samples/CreateBatchJobWithDeleteObject.cs b/storagebatchoperations/api/StorageBatchOperations.Samples/CreateBatchJobWithDeleteObject.cs
new file mode 100644
index 00000000000..f180ba0af4f
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.Samples/CreateBatchJobWithDeleteObject.cs
@@ -0,0 +1,59 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License").
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// [START storage_batch_create_job]
+
+using Google.Api.Gax.ResourceNames;
+using Google.Cloud.StorageBatchOperations.V1;
+using Google.LongRunning;
+using System;
+
+public class CreateBatchJobWithDeleteObjectSample
+{
+ ///
+ /// Creates a storage batch operation job with the option of delete objects.
+ ///
+ /// A resource name with pattern projects/{project}/locations/{location}.
+ /// A bucket list contains list of buckets and their objects to be transformed.
+ /// It is id for the job and it should not be more than 128 characters and must include only
+ /// characters available in DNS names, as defined by RFC-1123.
+ public Job CreateBatchJobWithDeleteObject(LocationName locationName,
+ BucketList bucketList,
+ string jobId = "your-job-id")
+ {
+ StorageBatchOperationsClient operationsClient = StorageBatchOperationsClient.Create();
+
+ Job job = new Job
+ {
+ DeleteObject = new DeleteObject { PermanentObjectDeletionEnabled = true },
+ BucketList = bucketList
+ };
+
+ // Create a job request with the specified location, job ID, and job details.
+ CreateJobRequest request = new CreateJobRequest
+ {
+ ParentAsLocationName = locationName,
+ JobId = jobId,
+ Job = job,
+ RequestId = jobId,
+ };
+
+ Operation response = operationsClient.CreateJob(request);
+ Operation completedResponse = response.PollUntilCompleted();
+ Job result = completedResponse.Result;
+ Console.WriteLine($"The Storage Batch Operation Job (Name: {result.Name}) is created with the option of Delete Objects");
+ return result;
+ }
+}
+// [END storage_batch_create_job]
diff --git a/storagebatchoperations/api/StorageBatchOperations.Samples/CreateBatchJobWithPutMetadata.cs b/storagebatchoperations/api/StorageBatchOperations.Samples/CreateBatchJobWithPutMetadata.cs
new file mode 100644
index 00000000000..84cdd6a0fbb
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.Samples/CreateBatchJobWithPutMetadata.cs
@@ -0,0 +1,69 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License").
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// [START storage_batch_create_job]
+
+using Google.Api.Gax.ResourceNames;
+using Google.Cloud.StorageBatchOperations.V1;
+using Google.LongRunning;
+using System;
+
+public class CreateBatchJobWithPutMetadataSample
+{
+ ///
+ /// Creates a storage batch operation job with the options of object metadata update.
+ ///
+ /// A resource name with pattern projects/{project}/locations/{location}.
+ /// A bucket list contains list of buckets and their objects to be transformed.
+ /// It is id for the job and it should not be more than 128 characters and must include only
+ /// characters available in DNS names, as defined by RFC-1123.
+ public Job CreateBatchJobWithPutMetadata(LocationName locationName,
+ BucketList bucketList,
+ string jobId = "your-job-id")
+ {
+ StorageBatchOperationsClient operationsClient = StorageBatchOperationsClient.Create();
+
+ PutMetadata putMetadata = new PutMetadata
+ {
+ CacheControl = "no-cache",
+ ContentDisposition = "inline",
+ ContentEncoding = "gzip",
+ ContentLanguage = "en-US",
+ ContentType = "text/plain",
+ CustomTime = DateTime.UtcNow.ToString("o")
+ };
+
+ Job job = new Job
+ {
+ PutMetadata = putMetadata,
+ BucketList = bucketList
+ };
+
+ // Create a job request with the specified location, job ID, and job details.
+ CreateJobRequest request = new CreateJobRequest
+ {
+ ParentAsLocationName = locationName,
+ JobId = jobId,
+ Job = job,
+ RequestId = jobId,
+ };
+
+ Operation response = operationsClient.CreateJob(request);
+ Operation completedResponse = response.PollUntilCompleted();
+ Job result = completedResponse.Result;
+ Console.WriteLine($"The Storage Batch Operation Job (Name: {result.Name}) is created with the Object Metadata Update");
+ return result;
+ }
+}
+// [END storage_batch_create_job]
diff --git a/storagebatchoperations/api/StorageBatchOperations.Samples/CreateBatchJobWithPutObjectHold.cs b/storagebatchoperations/api/StorageBatchOperations.Samples/CreateBatchJobWithPutObjectHold.cs
new file mode 100644
index 00000000000..a9f92e86d12
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.Samples/CreateBatchJobWithPutObjectHold.cs
@@ -0,0 +1,64 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License").
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// [START storage_batch_create_job]
+
+using Google.Api.Gax.ResourceNames;
+using Google.Cloud.StorageBatchOperations.V1;
+using Google.LongRunning;
+using System;
+
+public class CreateBatchJobWithPutObjectHoldSample
+{
+ ///
+ /// Creates a storage batch operation job with the options to update object hold.
+ ///
+ /// A resource name with pattern projects/{project}/locations/{location}.
+ /// A bucket list contains list of buckets and their objects to be transformed.
+ /// It is id for the job and it should not be more than 128 characters and must include only
+ /// characters available in DNS names, as defined by RFC-1123.
+ public Job CreateBatchJobWithPutObjectHold(LocationName locationName,
+ BucketList bucketList,
+ string jobId = "your-job-id")
+ {
+ StorageBatchOperationsClient operationsClient = StorageBatchOperationsClient.Create();
+
+ PutObjectHold putObjectHold = new PutObjectHold
+ {
+ EventBasedHold = PutObjectHold.Types.HoldStatus.Set
+ };
+
+ Job job = new Job
+ {
+ PutObjectHold = putObjectHold,
+ BucketList = bucketList
+ };
+
+ // Create a job request with the specified location, job ID, and job details.
+ CreateJobRequest request = new CreateJobRequest
+ {
+ ParentAsLocationName = locationName,
+ JobId = jobId,
+ Job = job,
+ RequestId = jobId,
+ };
+
+ Operation response = operationsClient.CreateJob(request);
+ Operation completedResponse = response.PollUntilCompleted();
+ Job result = completedResponse.Result;
+ Console.WriteLine($"The Storage Batch Operation Job (Name: {result.Name}) is created with the Object Hold Update");
+ return result;
+ }
+}
+// [END storage_batch_create_job]
diff --git a/storagebatchoperations/api/StorageBatchOperations.Samples/CreateBatchJobWithRewriteObject.cs b/storagebatchoperations/api/StorageBatchOperations.Samples/CreateBatchJobWithRewriteObject.cs
new file mode 100644
index 00000000000..854fbbcf90f
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.Samples/CreateBatchJobWithRewriteObject.cs
@@ -0,0 +1,66 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License").
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// [START storage_batch_create_job]
+
+using Google.Api.Gax.ResourceNames;
+using Google.Cloud.StorageBatchOperations.V1;
+using Google.LongRunning;
+using System;
+
+public class CreateBatchJobWithRewriteObjectSample
+{
+ ///
+ /// Creates a storage batch operation job with the options for object rewrite.
+ ///
+ /// A resource name with pattern projects/{project}/locations/{location}.
+ /// A bucket list contains list of buckets and their objects to be transformed.
+ /// It is id for the job and it should not be more than 128 characters and must include only
+ /// characters available in DNS names, as defined by RFC-1123.
+ /// Resource name of the Cloud KMS key that will be used to encrypt the object.The Cloud
+ /// KMS key must be located in same location as the object.
+ /// -typed view over the resource name property./param>
+ public Job CreateBatchJobWithRewriteObject(LocationName locationName,
+ BucketList bucketList,
+ string jobId = "your-job-id",
+ string kmsKeyName = "your-kms-key",
+ CryptoKeyName cryptoKeyName = null)
+ {
+ StorageBatchOperationsClient operationsClient = StorageBatchOperationsClient.Create();
+
+ RewriteObject rewriteObject = new RewriteObject { KmsKey = kmsKeyName, KmsKeyAsCryptoKeyName = cryptoKeyName };
+
+ Job job = new Job
+ {
+ RewriteObject = rewriteObject,
+ BucketList = bucketList
+ };
+
+ // Create a job request with the specified location, job ID, and job details.
+ CreateJobRequest request = new CreateJobRequest
+ {
+ ParentAsLocationName = locationName,
+ JobId = jobId,
+ Job = job,
+ RequestId = jobId,
+ };
+
+ Operation response = operationsClient.CreateJob(request);
+ Operation completedResponse = response.PollUntilCompleted();
+ Job result = completedResponse.Result;
+ Console.WriteLine($"The Storage Batch Operation Job (Name: {result.Name}) is created with the Rewrite Object");
+ return result;
+ }
+}
+// [END storage_batch_create_job]
diff --git a/storagebatchoperations/api/StorageBatchOperations.Samples/DeleteBatchJob.cs b/storagebatchoperations/api/StorageBatchOperations.Samples/DeleteBatchJob.cs
new file mode 100644
index 00000000000..7ce7fc2dbbe
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.Samples/DeleteBatchJob.cs
@@ -0,0 +1,39 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License").
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// [START storage_batch_delete_job]
+
+using Google.Cloud.StorageBatchOperations.V1;
+using System;
+
+public class DeleteBatchJobSample
+{
+ ///
+ /// Deletes a storage batch operation job.
+ ///
+ /// The name of the job to delete. Format: projects/{project_id}/locations/{location_id}/jobs/{job_id}.
+ public void DeleteBatchJob(string jobName = "projects/{project_id}/locations/{location_id}/jobs/{job_id}")
+ {
+ StorageBatchOperationsClient operationsClient = StorageBatchOperationsClient.Create();
+ // Create a request to delete the job.
+ DeleteJobRequest request = new DeleteJobRequest
+ {
+ Name = jobName
+ };
+ // Delete the job.
+ operationsClient.DeleteJob(request);
+ Console.WriteLine($"The Storage Batch Operation Job (Name : {jobName}) is deleted");
+ }
+}
+// [END storage_batch_delete_job]
diff --git a/storagebatchoperations/api/StorageBatchOperations.Samples/GetBatchJob.cs b/storagebatchoperations/api/StorageBatchOperations.Samples/GetBatchJob.cs
new file mode 100644
index 00000000000..80f0f4c54fd
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.Samples/GetBatchJob.cs
@@ -0,0 +1,39 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License").
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// [START storage_batch_get_job]
+
+using Google.Cloud.StorageBatchOperations.V1;
+using System;
+
+public class GetBatchJobSample
+{
+ ///
+ /// Gets a storage batch operation job.
+ ///
+ /// The name of the job to get. Format: projects/{project_id}/locations/{location_id}/jobs/{job_id}.
+ public Job GetBatchJob(string jobName = "projects/{project_id}/locations/{location_id}/jobs/{job_id}")
+ {
+ StorageBatchOperationsClient operationsClient = StorageBatchOperationsClient.Create();
+ // Create a request to get the job.
+ GetJobRequest request = new GetJobRequest
+ {
+ Name = jobName
+ };
+ var response = operationsClient.GetJob(request);
+ Console.WriteLine($"The Name of Storage Batch Operation Job is : {response.Name}");
+ return response;
+ }
+}
+// [END storage_batch_get_job]
diff --git a/storagebatchoperations/api/StorageBatchOperations.Samples/ListBatchJobs.cs b/storagebatchoperations/api/StorageBatchOperations.Samples/ListBatchJobs.cs
new file mode 100644
index 00000000000..52835cf6f90
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.Samples/ListBatchJobs.cs
@@ -0,0 +1,62 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License").
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// [START storage_batch_list_jobs]
+
+using Google.Api.Gax;
+using Google.Api.Gax.ResourceNames;
+using Google.Cloud.StorageBatchOperations.V1;
+using System;
+using System.Collections.Generic;
+
+public class ListBatchJobsSample
+{
+ ///
+ /// Lists storage batch operation jobs.
+ ///
+ /// A resource name with pattern projects/{project}/locations/{location}.
+ /// The field to filter the list of storage batch operation jobs.
+ /// The page size to retrieve page of known size.
+ /// The field to sort the list of storage batch operation jobs. Supported fields are name and create_time.
+ public IEnumerable ListBatchJobs(LocationName locationName,
+ string filter = "state:failed",
+ int pageSize = 100,
+ string orderBy = "name")
+ {
+ StorageBatchOperationsClient operationsClient = StorageBatchOperationsClient.Create();
+ // Create a request to list the batch jobs.
+ ListJobsRequest request = new ListJobsRequest
+ {
+ ParentAsLocationName = locationName,
+ Filter = filter,
+ OrderBy = orderBy
+ };
+
+ PagedEnumerable response = operationsClient.ListJobs(request);
+ Console.WriteLine("Storage Batch Operation Jobs are as follows:");
+ foreach (var item in response)
+ {
+ Console.WriteLine(item);
+ }
+ // Retrieve a single page of known size
+ Page singlePage = response.ReadPage(pageSize);
+ Console.WriteLine($"A single page of {pageSize} page size of Storage Batch Operation Jobs are as follows:");
+ foreach (Job item in singlePage)
+ {
+ Console.WriteLine(item);
+ }
+ return response;
+ }
+}
+// [END storage_batch_list_jobs]
diff --git a/storagebatchoperations/api/StorageBatchOperations.Samples/StorageBatchOperations.Samples.csproj b/storagebatchoperations/api/StorageBatchOperations.Samples/StorageBatchOperations.Samples.csproj
new file mode 100644
index 00000000000..e46f742aecc
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.Samples/StorageBatchOperations.Samples.csproj
@@ -0,0 +1,12 @@
+
+
+
+ netstandard2.1
+
+
+
+
+
+
+
+
diff --git a/storagebatchoperations/api/StorageBatchOperations.sln b/storagebatchoperations/api/StorageBatchOperations.sln
new file mode 100644
index 00000000000..c3f098d101a
--- /dev/null
+++ b/storagebatchoperations/api/StorageBatchOperations.sln
@@ -0,0 +1,62 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.11.35303.130
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StorageBatchOperations.Samples", "StorageBatchOperations.Samples\StorageBatchOperations.Samples.csproj", "{11EFF973-23FE-41B6-82D5-E8A0BA79F21C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StorageBatchOperations.Samples.Tests", "StorageBatchOperations.Samples.Tests\StorageBatchOperations.Samples.Tests.csproj", "{2BB74746-319D-465B-AAD5-C4430322501E}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {11EFF973-23FE-41B6-82D5-E8A0BA79F21C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {11EFF973-23FE-41B6-82D5-E8A0BA79F21C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {11EFF973-23FE-41B6-82D5-E8A0BA79F21C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {11EFF973-23FE-41B6-82D5-E8A0BA79F21C}.Debug|x64.Build.0 = Debug|Any CPU
+ {11EFF973-23FE-41B6-82D5-E8A0BA79F21C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {11EFF973-23FE-41B6-82D5-E8A0BA79F21C}.Debug|x86.Build.0 = Debug|Any CPU
+ {11EFF973-23FE-41B6-82D5-E8A0BA79F21C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {11EFF973-23FE-41B6-82D5-E8A0BA79F21C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {11EFF973-23FE-41B6-82D5-E8A0BA79F21C}.Release|x64.ActiveCfg = Release|Any CPU
+ {11EFF973-23FE-41B6-82D5-E8A0BA79F21C}.Release|x64.Build.0 = Release|Any CPU
+ {11EFF973-23FE-41B6-82D5-E8A0BA79F21C}.Release|x86.ActiveCfg = Release|Any CPU
+ {11EFF973-23FE-41B6-82D5-E8A0BA79F21C}.Release|x86.Build.0 = Release|Any CPU
+ {2BB74746-319D-465B-AAD5-C4430322501E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2BB74746-319D-465B-AAD5-C4430322501E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2BB74746-319D-465B-AAD5-C4430322501E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2BB74746-319D-465B-AAD5-C4430322501E}.Debug|x64.Build.0 = Debug|Any CPU
+ {2BB74746-319D-465B-AAD5-C4430322501E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {2BB74746-319D-465B-AAD5-C4430322501E}.Debug|x86.Build.0 = Debug|Any CPU
+ {2BB74746-319D-465B-AAD5-C4430322501E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2BB74746-319D-465B-AAD5-C4430322501E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2BB74746-319D-465B-AAD5-C4430322501E}.Release|x64.ActiveCfg = Release|Any CPU
+ {2BB74746-319D-465B-AAD5-C4430322501E}.Release|x64.Build.0 = Release|Any CPU
+ {2BB74746-319D-465B-AAD5-C4430322501E}.Release|x86.ActiveCfg = Release|Any CPU
+ {2BB74746-319D-465B-AAD5-C4430322501E}.Release|x86.Build.0 = Release|Any CPU
+ {5F74B143-822C-4514-B5B9-796EF116E44B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5F74B143-822C-4514-B5B9-796EF116E44B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5F74B143-822C-4514-B5B9-796EF116E44B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5F74B143-822C-4514-B5B9-796EF116E44B}.Debug|x64.Build.0 = Debug|Any CPU
+ {5F74B143-822C-4514-B5B9-796EF116E44B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5F74B143-822C-4514-B5B9-796EF116E44B}.Debug|x86.Build.0 = Debug|Any CPU
+ {5F74B143-822C-4514-B5B9-796EF116E44B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5F74B143-822C-4514-B5B9-796EF116E44B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5F74B143-822C-4514-B5B9-796EF116E44B}.Release|x64.ActiveCfg = Release|Any CPU
+ {5F74B143-822C-4514-B5B9-796EF116E44B}.Release|x64.Build.0 = Release|Any CPU
+ {5F74B143-822C-4514-B5B9-796EF116E44B}.Release|x86.ActiveCfg = Release|Any CPU
+ {5F74B143-822C-4514-B5B9-796EF116E44B}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {FD4D03FF-F99F-48B7-92EA-B9A73DF78693}
+ EndGlobalSection
+EndGlobal
diff --git a/storagebatchoperations/api/runTests.ps1 b/storagebatchoperations/api/runTests.ps1
new file mode 100644
index 00000000000..93af30db874
--- /dev/null
+++ b/storagebatchoperations/api/runTests.ps1
@@ -0,0 +1,16 @@
+# Copyright 2025 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License").
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+dotnet restore --force
+dotnet test --no-restore --test-adapter-path:. --logger:junit 2>&1 | %{ "$_" }